Merge "Update support lib to use correct current SDK DO NOT MERGE" into mnc-ub-dev
diff --git a/annotations/build.gradle b/annotations/build.gradle
index 2ff9980..755830f 100644
--- a/annotations/build.gradle
+++ b/annotations/build.gradle
@@ -1,5 +1,6 @@
 apply plugin: 'java'
-
+sourceCompatibility = JavaVersion.VERSION_1_7
+targetCompatibility = JavaVersion.VERSION_1_7
 archivesBaseName = 'support-annotations'
 
 sourceSets {
diff --git a/annotations/src/android/support/annotation/VisibleForTesting.java b/annotations/src/android/support/annotation/VisibleForTesting.java
index bb02ab4..0c893ff 100644
--- a/annotations/src/android/support/annotation/VisibleForTesting.java
+++ b/annotations/src/android/support/annotation/VisibleForTesting.java
@@ -17,12 +17,12 @@
 
 import java.lang.annotation.Retention;
 
-import static java.lang.annotation.RetentionPolicy.SOURCE;
+import static java.lang.annotation.RetentionPolicy.CLASS;
 
 /**
  * Denotes that the class, method or field has its visibility relaxed, so that it is more widely
  * visible than otherwise necessary to make code testable.
  */
-@Retention(SOURCE)
+@Retention(CLASS)
 public @interface VisibleForTesting {
 }
diff --git a/build.gradle b/build.gradle
index ae0817b..56f0479 100644
--- a/build.gradle
+++ b/build.gradle
@@ -133,7 +133,7 @@
 subprojects {
     // Change buildDir first so that all plugins pick up the new value.
     project.buildDir = project.file("$project.parent.buildDir/../$project.name/build")
-
+    project.ext.currentSdk = 'current'
     apply plugin: 'maven'
 
     version = rootProject.ext.supportVersion
@@ -153,6 +153,19 @@
 
                 // Disable unique names for SNAPSHOTS so they can be updated in place.
                 setUniqueVersion(false)
+                doLast {
+                    // Remove any invalid maven-metadata.xml files that may have been created
+                    // for SNAPSHOT versions that are *not* uniquely versioned.
+                    pom*.each { pom ->
+                        if (pom.version.endsWith('-SNAPSHOT')) {
+                            final File artifactDir = new File(rootProject.ext.supportRepoOut,
+                                    pom.groupId.replace('.', '/')
+                                    + '/' + pom.artifactId
+                                    + '/' + pom.version)
+                            delete fileTree(dir: artifactDir, include: 'maven-metadata.xml*')
+                        }
+                    }
+                }
            }
         }
     }
diff --git a/customtabs/build.gradle b/customtabs/build.gradle
index 317dd9d..4cbfb88 100644
--- a/customtabs/build.gradle
+++ b/customtabs/build.gradle
@@ -1,4 +1,4 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
 
 archivesBaseName = 'customtabs'
 
@@ -8,7 +8,7 @@
 }
 
 android {
-    compileSdkVersion 'current'
+    compileSdkVersion project.ext.currentSdk
 
     sourceSets {
         main.manifest.srcFile 'AndroidManifest.xml'
diff --git a/design/build.gradle b/design/build.gradle
index 7de5f39..f7726b0 100644
--- a/design/build.gradle
+++ b/design/build.gradle
@@ -1,4 +1,4 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
 
 archivesBaseName = 'design'
 
@@ -17,7 +17,7 @@
 }
 
 android {
-    compileSdkVersion 'current'
+    compileSdkVersion project.ext.currentSdk
 
     defaultConfig {
         minSdkVersion 7
diff --git a/design/res/values/attrs.xml b/design/res/values/attrs.xml
index 7ff70c2..7a9e3c1 100644
--- a/design/res/values/attrs.xml
+++ b/design/res/values/attrs.xml
@@ -109,7 +109,7 @@
         <attr name="android:text" />
         <!-- Icon to display in the tab. -->
         <attr name="android:icon" />
-        <!-- A reference to a layout to be displayed in the tab. -->
+        <!-- A reference to a layout resource to be displayed in the tab. -->
         <attr name="android:layout" />
     </declare-styleable>
 
diff --git a/design/src/android/support/design/internal/NavigationMenuPresenter.java b/design/src/android/support/design/internal/NavigationMenuPresenter.java
index cb95f4f..609795f 100644
--- a/design/src/android/support/design/internal/NavigationMenuPresenter.java
+++ b/design/src/android/support/design/internal/NavigationMenuPresenter.java
@@ -238,12 +238,14 @@
         updateMenuView(false);
     }
 
+    @Nullable
     public Drawable getItemBackground() {
         return mItemBackground;
     }
 
-    public void setItemBackground(Drawable itemBackground) {
+    public void setItemBackground(@Nullable Drawable itemBackground) {
         mItemBackground = itemBackground;
+        updateMenuView(false);
     }
 
     public void setUpdateSuspended(boolean updateSuspended) {
diff --git a/design/src/android/support/design/widget/BottomSheetBehavior.java b/design/src/android/support/design/widget/BottomSheetBehavior.java
index aa39ddd..1be223d 100644
--- a/design/src/android/support/design/widget/BottomSheetBehavior.java
+++ b/design/src/android/support/design/widget/BottomSheetBehavior.java
@@ -198,6 +198,9 @@
 
     @Override
     public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) {
+        if (!child.isShown()) {
+            return false;
+        }
         int action = MotionEventCompat.getActionMasked(event);
         switch (action) {
             case MotionEvent.ACTION_UP:
@@ -227,6 +230,9 @@
 
     @Override
     public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) {
+        if (!child.isShown()) {
+            return false;
+        }
         mViewDragHelper.processTouchEvent(event);
         return true;
     }
diff --git a/design/src/android/support/design/widget/CollapsingTextHelper.java b/design/src/android/support/design/widget/CollapsingTextHelper.java
index 9a38c7d..8768826 100644
--- a/design/src/android/support/design/widget/CollapsingTextHelper.java
+++ b/design/src/android/support/design/widget/CollapsingTextHelper.java
@@ -105,8 +105,7 @@
     public CollapsingTextHelper(View view) {
         mView = view;
 
-        mTextPaint = new TextPaint();
-        mTextPaint.setAntiAlias(true);
+        mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG | Paint.SUBPIXEL_TEXT_FLAG);
 
         mCollapsedBounds = new Rect();
         mExpandedBounds = new Rect();
@@ -436,10 +435,6 @@
 
             final float ascent;
             final float descent;
-
-            // Update the TextPaint to the current text size
-            mTextPaint.setTextSize(mCurrentTextSize);
-
             if (drawTexture) {
                 ascent = mTextureAscent * mScale;
                 descent = mTextureDescent * mScale;
@@ -536,6 +531,8 @@
         if (mTextToDraw == null || updateDrawText) {
             mTextPaint.setTextSize(mCurrentTextSize);
             mTextPaint.setTypeface(mCurrentTypeface);
+            // Use linear text scaling if we're scaling the canvas
+            mTextPaint.setLinearText(mScale != 1f);
 
             // If we don't currently have text to draw, or the text size has changed, ellipsize...
             final CharSequence title = TextUtils.ellipsize(mText, mTextPaint,
diff --git a/design/src/android/support/design/widget/NavigationView.java b/design/src/android/support/design/widget/NavigationView.java
index a73dc65..42f4eed 100644
--- a/design/src/android/support/design/widget/NavigationView.java
+++ b/design/src/android/support/design/widget/NavigationView.java
@@ -297,7 +297,7 @@
     }
 
     /**
-     * Returns the tint which is applied to our item's icons.
+     * Returns the tint which is applied to our menu items' icons.
      *
      * @see #setItemIconTintList(ColorStateList)
      *
@@ -309,7 +309,7 @@
     }
 
     /**
-     * Set the tint which is applied to our item's icons.
+     * Set the tint which is applied to our menu items' icons.
      *
      * @param tint the tint to apply.
      *
@@ -320,7 +320,7 @@
     }
 
     /**
-     * Returns the tint which is applied to our item's icons.
+     * Returns the tint which is applied to our menu items' icons.
      *
      * @see #setItemTextColor(ColorStateList)
      *
@@ -332,7 +332,7 @@
     }
 
     /**
-     * Set the text color which is text to our items.
+     * Set the text color to be used on our menu items.
      *
      * @see #getItemTextColor()
      *
@@ -343,18 +343,19 @@
     }
 
     /**
-     * Returns the background drawable for the menu items.
+     * Returns the background drawable for our menu items.
      *
      * @see #setItemBackgroundResource(int)
      *
      * @attr ref R.styleable#NavigationView_itemBackground
      */
+    @Nullable
     public Drawable getItemBackground() {
         return mPresenter.getItemBackground();
     }
 
     /**
-     * Set the background of the menu items to the given resource.
+     * Set the background of our menu items to the given resource.
      *
      * @param resId The identifier of the resource.
      *
@@ -365,12 +366,12 @@
     }
 
     /**
-     * Set the background of the menu items to a given resource. The resource should refer to
-     * a Drawable object or 0 to use the background background.
+     * Set the background of our menu items to a given resource. The resource should refer to
+     * a Drawable object or null to use the default background set on this navigation menu.
      *
      * @attr ref R.styleable#NavigationView_itemBackground
      */
-    public void setItemBackground(Drawable itemBackground) {
+    public void setItemBackground(@Nullable Drawable itemBackground) {
         mPresenter.setItemBackground(itemBackground);
     }
 
diff --git a/design/tests/AndroidManifest.xml b/design/tests/AndroidManifest.xml
index af26af3..4f65f92 100755
--- a/design/tests/AndroidManifest.xml
+++ b/design/tests/AndroidManifest.xml
@@ -39,6 +39,10 @@
                 android:name="android.support.design.widget.BottomSheetBehaviorActivity"/>
 
         <activity
+            android:theme="@style/Theme.AppCompat.NoActionBar"
+            android:name="android.support.design.widget.NavigationViewActivity"/>
+
+        <activity
                 android:name="android.support.v7.app.AppCompatActivity"/>
 
     </application>
diff --git a/v7/appcompat/res/values-ldrtl-v23/styles_base.xml b/design/tests/res/color/color_state_list_lilac.xml
similarity index 69%
copy from v7/appcompat/res/values-ldrtl-v23/styles_base.xml
copy to design/tests/res/color/color_state_list_lilac.xml
index cba56bd..f0b2791 100644
--- a/v7/appcompat/res/values-ldrtl-v23/styles_base.xml
+++ b/design/tests/res/color/color_state_list_lilac.xml
@@ -1,6 +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.
@@ -15,8 +14,8 @@
      limitations under the License.
 -->
 
-<resources>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false" android:color="@color/lilac_disabled" />
+    <item android:color="@color/lilac_default"/>
+</selector>
 
-    <style name="Base.Widget.AppCompat.Spinner.Underlined" parent="android:Widget.Material.Spinner.Underlined" />
-
-</resources>
diff --git a/v7/appcompat/res/values-ldrtl-v23/styles_base.xml b/design/tests/res/color/color_state_list_red_translucent.xml
similarity index 74%
rename from v7/appcompat/res/values-ldrtl-v23/styles_base.xml
rename to design/tests/res/color/color_state_list_red_translucent.xml
index cba56bd..fdf8b2b 100644
--- a/v7/appcompat/res/values-ldrtl-v23/styles_base.xml
+++ b/design/tests/res/color/color_state_list_red_translucent.xml
@@ -1,6 +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.
@@ -15,8 +14,7 @@
      limitations under the License.
 -->
 
-<resources>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@color/red_translucent"/>
+</selector>
 
-    <style name="Base.Widget.AppCompat.Spinner.Underlined" parent="android:Widget.Material.Spinner.Underlined" />
-
-</resources>
diff --git a/v7/appcompat/res/values-ldrtl-v23/styles_base.xml b/design/tests/res/color/color_state_list_sand.xml
similarity index 63%
copy from v7/appcompat/res/values-ldrtl-v23/styles_base.xml
copy to design/tests/res/color/color_state_list_sand.xml
index cba56bd..eb472c2 100644
--- a/v7/appcompat/res/values-ldrtl-v23/styles_base.xml
+++ b/design/tests/res/color/color_state_list_sand.xml
@@ -1,6 +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.
@@ -15,8 +14,9 @@
      limitations under the License.
 -->
 
-<resources>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false" android:color="@color/sand_disabled" />
+    <item android:state_checked="true" android:color="@color/sand_checked" />
+    <item android:color="@color/sand_default" />
+</selector>
 
-    <style name="Base.Widget.AppCompat.Spinner.Underlined" parent="android:Widget.Material.Spinner.Underlined" />
-
-</resources>
diff --git a/design/tests/res/drawable-mdpi/test_drawable_blue.png b/design/tests/res/drawable-mdpi/test_drawable_blue.png
new file mode 100644
index 0000000..6350c14
--- /dev/null
+++ b/design/tests/res/drawable-mdpi/test_drawable_blue.png
Binary files differ
diff --git a/design/tests/res/drawable-mdpi/test_drawable_green.png b/design/tests/res/drawable-mdpi/test_drawable_green.png
new file mode 100644
index 0000000..e3afddb
--- /dev/null
+++ b/design/tests/res/drawable-mdpi/test_drawable_green.png
Binary files differ
diff --git a/design/tests/res/drawable-mdpi/test_drawable_red.png b/design/tests/res/drawable-mdpi/test_drawable_red.png
new file mode 100644
index 0000000..d95db88
--- /dev/null
+++ b/design/tests/res/drawable-mdpi/test_drawable_red.png
Binary files differ
diff --git a/v7/appcompat/res/values-ldrtl-v23/styles_base.xml b/design/tests/res/drawable/test_background_blue.xml
similarity index 73%
copy from v7/appcompat/res/values-ldrtl-v23/styles_base.xml
copy to design/tests/res/drawable/test_background_blue.xml
index cba56bd..fe4bca2 100644
--- a/v7/appcompat/res/values-ldrtl-v23/styles_base.xml
+++ b/design/tests/res/drawable/test_background_blue.xml
@@ -1,6 +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.
@@ -15,8 +14,9 @@
      limitations under the License.
 -->
 
-<resources>
-
-    <style name="Base.Widget.AppCompat.Spinner.Underlined" parent="android:Widget.Material.Spinner.Underlined" />
-
-</resources>
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid
+        android:color="@color/test_blue" />
+</shape>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-ldrtl-v23/styles_base.xml b/design/tests/res/drawable/test_background_green.xml
similarity index 72%
copy from v7/appcompat/res/values-ldrtl-v23/styles_base.xml
copy to design/tests/res/drawable/test_background_green.xml
index cba56bd..b90d9bc 100644
--- a/v7/appcompat/res/values-ldrtl-v23/styles_base.xml
+++ b/design/tests/res/drawable/test_background_green.xml
@@ -1,6 +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.
@@ -15,8 +14,9 @@
      limitations under the License.
 -->
 
-<resources>
-
-    <style name="Base.Widget.AppCompat.Spinner.Underlined" parent="android:Widget.Material.Spinner.Underlined" />
-
-</resources>
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid
+        android:color="@color/test_green" />
+</shape>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-ldrtl-v23/styles_base.xml b/design/tests/res/drawable/test_drawable_state_list.xml
similarity index 67%
copy from v7/appcompat/res/values-ldrtl-v23/styles_base.xml
copy to design/tests/res/drawable/test_drawable_state_list.xml
index cba56bd..f125913 100644
--- a/v7/appcompat/res/values-ldrtl-v23/styles_base.xml
+++ b/design/tests/res/drawable/test_drawable_state_list.xml
@@ -1,6 +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.
@@ -15,8 +14,8 @@
      limitations under the License.
 -->
 
-<resources>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_checked="true" android:drawable="@drawable/test_background_blue" />
+    <item android:drawable="@drawable/test_background_green" />
+</selector>
 
-    <style name="Base.Widget.AppCompat.Spinner.Underlined" parent="android:Widget.Material.Spinner.Underlined" />
-
-</resources>
diff --git a/v7/appcompat/res/values-ldrtl-v23/styles_base.xml b/design/tests/res/layout/action_layout.xml
similarity index 63%
copy from v7/appcompat/res/values-ldrtl-v23/styles_base.xml
copy to design/tests/res/layout/action_layout.xml
index cba56bd..8c4dc05 100644
--- a/v7/appcompat/res/values-ldrtl-v23/styles_base.xml
+++ b/design/tests/res/layout/action_layout.xml
@@ -1,6 +1,6 @@
 <?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.
@@ -14,9 +14,10 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-
-<resources>
-
-    <style name="Base.Widget.AppCompat.Spinner.Underlined" parent="android:Widget.Material.Spinner.Underlined" />
-
-</resources>
+<android.support.v7.widget.SwitchCompat
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/toggle"
+    android:orientation="vertical"
+    android:layout_width="wrap_content"
+    android:layout_height="match_parent"
+    android:gravity="center_vertical"/>
diff --git a/design/tests/res/layout/design_navigation_view.xml b/design/tests/res/layout/design_navigation_view.xml
new file mode 100644
index 0000000..1789843
--- /dev/null
+++ b/design/tests/res/layout/design_navigation_view.xml
@@ -0,0 +1,54 @@
+<?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.DrawerLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/drawer_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true">
+    <!-- As the main content view, the view below consumes the entire
+         space available using match_parent in both dimensions. Note that
+         this child does not specify android:layout_gravity attribute. -->
+    <FrameLayout
+        android:id="@+id/content"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+    <!-- android:layout_gravity="start" tells DrawerLayout to treat
+         this as a sliding drawer on the starting side, which is
+         left for left-to-right locales. The navigation view extends
+         the full height of the container. A
+         solid background is used for contrast with the content view.
+         android:fitsSystemWindows="true" tells the system to have
+         DrawerLayout span the full height of the screen, including the
+         system status bar on Lollipop+ versions of the plaform. -->
+    <android.support.design.widget.NavigationView
+        android:id="@+id/start_drawer"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_gravity="start"
+        android:background="#333"
+        android:fitsSystemWindows="true"
+        app:menu="@menu/navigation_view_content"
+        app:itemIconTint="@color/emerald_translucent"
+        app:itemTextColor="@color/emerald_text"
+        app:itemBackground="@color/sand_default"
+        app:itemTextAppearance="@style/TextMediumStyle" />
+
+</android.support.v4.widget.DrawerLayout>
+
diff --git a/v7/appcompat/res/values-ldrtl-v23/styles_base.xml b/design/tests/res/layout/design_navigation_view_header1.xml
similarity index 68%
copy from v7/appcompat/res/values-ldrtl-v23/styles_base.xml
copy to design/tests/res/layout/design_navigation_view_header1.xml
index cba56bd..2fd6f20 100644
--- a/v7/appcompat/res/values-ldrtl-v23/styles_base.xml
+++ b/design/tests/res/layout/design_navigation_view_header1.xml
@@ -1,6 +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.
@@ -15,8 +14,10 @@
      limitations under the License.
 -->
 
-<resources>
+<View
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/header1"
+    android:layout_width="match_parent"
+    android:layout_height="120dip"
+    android:background="@color/test_red" />
 
-    <style name="Base.Widget.AppCompat.Spinner.Underlined" parent="android:Widget.Material.Spinner.Underlined" />
-
-</resources>
diff --git a/v7/appcompat/res/values-ldrtl-v23/styles_base.xml b/design/tests/res/layout/design_navigation_view_header2.xml
similarity index 68%
copy from v7/appcompat/res/values-ldrtl-v23/styles_base.xml
copy to design/tests/res/layout/design_navigation_view_header2.xml
index cba56bd..77ad32a 100644
--- a/v7/appcompat/res/values-ldrtl-v23/styles_base.xml
+++ b/design/tests/res/layout/design_navigation_view_header2.xml
@@ -1,6 +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.
@@ -15,8 +14,10 @@
      limitations under the License.
 -->
 
-<resources>
+<View
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/header2"
+    android:layout_width="match_parent"
+    android:layout_height="100dip"
+    android:background="@color/test_blue" />
 
-    <style name="Base.Widget.AppCompat.Spinner.Underlined" parent="android:Widget.Material.Spinner.Underlined" />
-
-</resources>
diff --git a/v7/appcompat/res/values-ldrtl-v23/styles_base.xml b/design/tests/res/layout/design_navigation_view_header3.xml
similarity index 68%
copy from v7/appcompat/res/values-ldrtl-v23/styles_base.xml
copy to design/tests/res/layout/design_navigation_view_header3.xml
index cba56bd..b1fd676 100644
--- a/v7/appcompat/res/values-ldrtl-v23/styles_base.xml
+++ b/design/tests/res/layout/design_navigation_view_header3.xml
@@ -1,6 +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.
@@ -15,8 +14,10 @@
      limitations under the License.
 -->
 
-<resources>
+<View
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/header3"
+    android:layout_width="match_parent"
+    android:layout_height="80dip"
+    android:background="@color/test_green" />
 
-    <style name="Base.Widget.AppCompat.Spinner.Underlined" parent="android:Widget.Material.Spinner.Underlined" />
-
-</resources>
diff --git a/design/tests/res/menu/navigation_view_content.xml b/design/tests/res/menu/navigation_view_content.xml
new file mode 100644
index 0000000..758334f
--- /dev/null
+++ b/design/tests/res/menu/navigation_view_content.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 Google Inc.
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<menu
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <group android:checkableBehavior="single">
+        <item android:id="@+id/destination_home"
+              android:title="@string/navigate_home"
+              android:icon="@drawable/test_drawable_red" />
+        <item android:id="@+id/destination_profile"
+              android:title="@string/navigate_profile"
+              android:icon="@drawable/test_drawable_green" />
+        <item android:id="@+id/destination_people"
+              android:title="@string/navigate_people"
+              android:icon="@drawable/test_drawable_blue"
+              app:actionLayout="@layout/action_layout" />
+        <item android:id="@+id/destination_settings"
+              android:title="@string/navigate_settings" />
+    </group>
+</menu>
diff --git a/design/tests/res/values/colors.xml b/design/tests/res/values/colors.xml
new file mode 100644
index 0000000..ea8da2a
--- /dev/null
+++ b/design/tests/res/values/colors.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.
+-->
+
+<resources>
+    <color name="emerald_translucent">#8020A060</color>
+    <color name="emerald_text">#30B050</color>
+    <color name="red_translucent">#90FF2040</color>
+
+    <color name="lilac_default">#F080F0</color>
+    <color name="lilac_disabled">#F0A0FF</color>
+    <color name="sand_default">#F0B000</color>
+    <color name="sand_disabled">#FFC080</color>
+    <color name="sand_checked">#FFD0A0</color>
+
+    <color name="test_red">#FF6030</color>
+    <color name="test_green">#50E080</color>
+    <color name="test_blue">#3050CF</color>
+</resources>
diff --git a/design/tests/res/values/dimens.xml b/design/tests/res/values/dimens.xml
index c20e4f8..94c2734 100644
--- a/design/tests/res/values/dimens.xml
+++ b/design/tests/res/values/dimens.xml
@@ -18,4 +18,11 @@
     <dimen name="tab_width_limit_medium">100dip</dimen>
     <dimen name="tab_width_limit_large">120dip</dimen>
     <dimen name="bottom_sheet_peek_height">128dp</dimen>
+
+    <dimen name="text_small_size">16sp</dimen>
+    <dimen name="text_medium_size">20sp</dimen>
+
+    <dimen name="drawable_small_size">12dip</dimen>
+    <dimen name="drawable_medium_size">16dip</dimen>
+    <dimen name="drawable_large_size">20dip</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 854e711..05dd55e 100644
--- a/design/tests/res/values/strings.xml
+++ b/design/tests/res/values/strings.xml
@@ -15,4 +15,9 @@
 -->
 <resources>
     <string name="tab_layout_text">Tab text!</string>
+
+    <string name="navigate_home">Home</string>
+    <string name="navigate_profile">Profile</string>
+    <string name="navigate_people">People</string>
+    <string name="navigate_settings">Settings</string>
 </resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-ldrtl-v23/styles_base.xml b/design/tests/res/values/styles.xml
similarity index 62%
copy from v7/appcompat/res/values-ldrtl-v23/styles_base.xml
copy to design/tests/res/values/styles.xml
index cba56bd..4fc946b 100644
--- a/v7/appcompat/res/values-ldrtl-v23/styles_base.xml
+++ b/design/tests/res/values/styles.xml
@@ -1,6 +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.
@@ -14,9 +13,12 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-
 <resources>
+    <style name="TextSmallStyle" parent="@android:style/TextAppearance">
+        <item name="android:textSize">@dimen/text_small_size</item>
+    </style>
 
-    <style name="Base.Widget.AppCompat.Spinner.Underlined" parent="android:Widget.Material.Spinner.Underlined" />
-
-</resources>
+    <style name="TextMediumStyle" parent="@android:style/TextAppearance.Medium">
+        <item name="android:textSize">@dimen/text_medium_size</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/design/tests/src/android/support/design/testutils/DrawerLayoutActions.java b/design/tests/src/android/support/design/testutils/DrawerLayoutActions.java
new file mode 100755
index 0000000..e4ea867
--- /dev/null
+++ b/design/tests/src/android/support/design/testutils/DrawerLayoutActions.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.design.testutils;
+
+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;
+
+import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
+
+public class DrawerLayoutActions {
+    /**
+     * Opens the drawer at the specified edge gravity.
+     */
+    public static ViewAction openDrawer(final int drawerEdgeGravity) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isAssignableFrom(DrawerLayout.class);
+            }
+
+            @Override
+            public String getDescription() {
+                return "Opens the drawer";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                DrawerLayout drawerLayout = (DrawerLayout) view;
+                drawerLayout.openDrawer(drawerEdgeGravity);
+
+                // Wait for a full second to let the inner ViewDragHelper complete the operation
+                uiController.loopMainThreadForAtLeast(1000);
+            }
+        };
+    }
+
+    /**
+     * Closes the drawer at the specified edge gravity.
+     */
+    public static ViewAction closeDrawer(final int drawerEdgeGravity) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isAssignableFrom(DrawerLayout.class);
+            }
+
+            @Override
+            public String getDescription() {
+                return "Closes the drawer";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                DrawerLayout drawerLayout = (DrawerLayout) view;
+                drawerLayout.closeDrawer(drawerEdgeGravity);
+
+                // Wait for a full second to let the inner ViewDragHelper complete the operation
+                uiController.loopMainThreadForAtLeast(1000);
+            }
+        };
+    }
+}
diff --git a/design/tests/src/android/support/design/testutils/NavigationViewActions.java b/design/tests/src/android/support/design/testutils/NavigationViewActions.java
new file mode 100644
index 0000000..bcc15da
--- /dev/null
+++ b/design/tests/src/android/support/design/testutils/NavigationViewActions.java
@@ -0,0 +1,308 @@
+/*
+ * 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.content.res.ColorStateList;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.IdRes;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.StyleRes;
+import android.support.design.widget.NavigationView;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.view.LayoutInflater;
+import android.view.View;
+import org.hamcrest.Matcher;
+
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+
+public class NavigationViewActions {
+    /**
+     * Sets item text appearance on the content of the navigation view.
+     */
+    public static ViewAction setItemTextAppearance(final @StyleRes int resId) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isDisplayed();
+            }
+
+            @Override
+            public String getDescription() {
+                return "Set item text appearance";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                NavigationView navigationView = (NavigationView) view;
+                navigationView.setItemTextAppearance(resId);
+
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
+
+    /**
+     * Sets item text color on the content of the navigation view.
+     */
+    public static ViewAction setItemTextColor(final ColorStateList textColor) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isDisplayed();
+            }
+
+            @Override
+            public String getDescription() {
+                return "Set item text color";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                NavigationView navigationView = (NavigationView) view;
+                navigationView.setItemTextColor(textColor);
+
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
+
+    /**
+     * Sets item background on the content of the navigation view.
+     */
+    public static ViewAction setItemBackground(final @Nullable Drawable itemBackground) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isDisplayed();
+            }
+
+            @Override
+            public String getDescription() {
+                return "Set item background";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                NavigationView navigationView = (NavigationView) view;
+                navigationView.setItemBackground(itemBackground);
+
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
+
+    /**
+     * Sets item background on the content of the navigation view.
+     */
+    public static ViewAction setItemBackgroundResource(final @DrawableRes int resId) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isDisplayed();
+            }
+
+            @Override
+            public String getDescription() {
+                return "Set item background";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                NavigationView navigationView = (NavigationView) view;
+                navigationView.setItemBackgroundResource(resId);
+
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
+
+    /**
+     * Sets item icon tint list on the content of the navigation view.
+     */
+    public static ViewAction setItemIconTintList(final @Nullable ColorStateList tint) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isDisplayed();
+            }
+
+            @Override
+            public String getDescription() {
+                return "Set item icon tint list";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                NavigationView navigationView = (NavigationView) view;
+                navigationView.setItemIconTintList(tint);
+
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
+
+    /**
+     * Add the specified view as a header to the navigation view.
+     */
+    public static ViewAction addHeaderView(final @NonNull LayoutInflater inflater,
+            final @LayoutRes int res) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isDisplayed();
+            }
+
+            @Override
+            public String getDescription() {
+                return "Add header view";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                NavigationView navigationView = (NavigationView) view;
+                navigationView.addHeaderView(inflater.inflate(res, null, false));
+
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
+
+    /**
+     * Inflates a view from the specified layout ID and adds it as a header to the navigation view.
+     */
+    public static ViewAction inflateHeaderView(final @LayoutRes int res) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isDisplayed();
+            }
+
+            @Override
+            public String getDescription() {
+                return "Inflate and add header view";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                NavigationView navigationView = (NavigationView) view;
+                navigationView.inflateHeaderView(res);
+
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
+
+    /**
+     * Removes a previously added header view from the navigation view.
+     */
+    public static ViewAction removeHeaderView(final @Nullable View headerView) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isDisplayed();
+            }
+
+            @Override
+            public String getDescription() {
+                return "Remove header view";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                NavigationView navigationView = (NavigationView) view;
+                navigationView.removeHeaderView(headerView);
+
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
+
+    /**
+     * Sets checked item on the navigation view.
+     */
+    public static ViewAction setCheckedItem(final @IdRes int id) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isDisplayed();
+            }
+
+            @Override
+            public String getDescription() {
+                return "Set checked item";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                NavigationView navigationView = (NavigationView) view;
+                navigationView.setCheckedItem(id);
+
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
+
+    /**
+     * Sets icon for the menu item of the navigation view.
+     */
+    public static ViewAction setIconForMenuItem(final @IdRes int menuItemId,
+            final Drawable iconDrawable) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isDisplayed();
+            }
+
+            @Override
+            public String getDescription() {
+                return "Set menu item icon";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                NavigationView navigationView = (NavigationView) view;
+                navigationView.getMenu().findItem(menuItemId).setIcon(iconDrawable);
+
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
+}
diff --git a/design/tests/src/android/support/design/testutils/TestUtils.java b/design/tests/src/android/support/design/testutils/TestUtils.java
new file mode 100644
index 0000000..3255fb1
--- /dev/null
+++ b/design/tests/src/android/support/design/testutils/TestUtils.java
@@ -0,0 +1,95 @@
+/*
+ * 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.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+import junit.framework.Assert;
+
+public class TestUtils {
+    /**
+     * 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
+     * <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 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);
+
+        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]);
+
+                    int expectedAlpha = Color.alpha(color);
+                    int expectedRed = Color.red(color);
+                    int expectedGreen = Color.green(color);
+                    int expectedBlue = Color.blue(color);
+
+                    int varianceAlpha = Math.abs(sourceAlpha - expectedAlpha);
+                    int varianceRed = Math.abs(sourceRed - expectedRed);
+                    int varianceGreen = Math.abs(sourceGreen - expectedGreen);
+                    int varianceBlue = Math.abs(sourceBlue - expectedBlue);
+
+                    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 + ") found ["
+                                + sourceAlpha + "," + sourceRed + ","
+                                + sourceGreen + "," + sourceBlue + "]";
+                        if (throwExceptionIfFails) {
+                            throw new RuntimeException(mismatchDescription);
+                        } else {
+                            Assert.fail(mismatchDescription);
+                        }
+                    }
+                }
+            }
+        } finally {
+            bitmap.recycle();
+        }
+    }
+}
\ No newline at end of file
diff --git a/design/tests/src/android/support/design/testutils/TestUtilsMatchers.java b/design/tests/src/android/support/design/testutils/TestUtilsMatchers.java
index 049e7a5..fae249c 100644
--- a/design/tests/src/android/support/design/testutils/TestUtilsMatchers.java
+++ b/design/tests/src/android/support/design/testutils/TestUtilsMatchers.java
@@ -16,10 +16,18 @@
 
 package android.support.design.testutils;
 
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.ColorInt;
 import android.support.test.espresso.matcher.BoundedMatcher;
+import android.support.v4.view.ViewCompat;
 import android.view.View;
+import android.view.ViewParent;
+import android.widget.TextView;
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
 
 public class TestUtilsMatchers {
     /**
@@ -71,4 +79,163 @@
             }
         };
     }
+
+    /**
+     * Returns a matcher that matches TextViews with the specified text size.
+     */
+    public static Matcher withTextSize(final float textSize) {
+        return new BoundedMatcher<View, TextView>(TextView.class) {
+            private String failedCheckDescription;
+
+            @Override
+            public void describeTo(final Description description) {
+                description.appendText(failedCheckDescription);
+            }
+
+            @Override
+            public boolean matchesSafely(final TextView view) {
+                final float ourTextSize = view.getTextSize();
+                if (Math.abs(textSize - ourTextSize) > 1.0f) {
+                    failedCheckDescription =
+                            "text size " + ourTextSize + " is different than expected " + textSize;
+                    return false;
+                }
+                return true;
+            }
+        };
+    }
+
+    /**
+     * Returns a matcher that matches TextViews with the specified text color.
+     */
+    public static Matcher withTextColor(final @ColorInt int textColor) {
+        return new BoundedMatcher<View, TextView>(TextView.class) {
+            private String failedCheckDescription;
+
+            @Override
+            public void describeTo(final Description description) {
+                description.appendText(failedCheckDescription);
+            }
+
+            @Override
+            public boolean matchesSafely(final TextView view) {
+                final @ColorInt int ourTextColor = view.getCurrentTextColor();
+                if (ourTextColor != textColor) {
+                    int ourAlpha = Color.alpha(ourTextColor);
+                    int ourRed = Color.red(ourTextColor);
+                    int ourGreen = Color.green(ourTextColor);
+                    int ourBlue = Color.blue(ourTextColor);
+
+                    int expectedAlpha = Color.alpha(textColor);
+                    int expectedRed = Color.red(textColor);
+                    int expectedGreen = Color.green(textColor);
+                    int expectedBlue = Color.blue(textColor);
+
+                    failedCheckDescription =
+                            "expected color to be ["
+                                    + expectedAlpha + "," + expectedRed + ","
+                                    + expectedGreen + "," + expectedBlue
+                                    + "] but found ["
+                                    + ourAlpha + "," + ourRed + ","
+                                    + ourGreen + "," + ourBlue + "]";
+                    return false;
+                }
+                return true;
+            }
+        };
+    }
+
+    /**
+     * Returns a matcher that matches TextViews whose start drawable is filled with the specified
+     * fill color.
+     */
+    public static Matcher withStartDrawableFilledWith(final @ColorInt int fillColor,
+            final int allowedComponentVariance) {
+        return new BoundedMatcher<View, TextView>(TextView.class) {
+            private String failedCheckDescription;
+
+            @Override
+            public void describeTo(final Description description) {
+                description.appendText(failedCheckDescription);
+            }
+
+            @Override
+            public boolean matchesSafely(final TextView view) {
+                final Drawable[] compoundDrawables = view.getCompoundDrawables();
+                final boolean isRtl =
+                        (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_RTL);
+                final Drawable startDrawable = isRtl ? compoundDrawables[2] : compoundDrawables[0];
+                if (startDrawable == null) {
+                    failedCheckDescription = "no start drawable";
+                    return false;
+                }
+                try {
+                    final Rect bounds = startDrawable.getBounds();
+                    TestUtils.assertAllPixelsOfColor("",
+                            startDrawable, bounds.width(), bounds.height(), true,
+                            fillColor, allowedComponentVariance, true);
+                } catch (Throwable t) {
+                    failedCheckDescription = t.getMessage();
+                    return false;
+                }
+                return true;
+            }
+        };
+    }
+
+    /**
+     * Returns a matcher that matches Views with the specified background fill color.
+     */
+    public static Matcher withBackgroundFill(final @ColorInt int fillColor) {
+        return new BoundedMatcher<View, View>(View.class) {
+            private String failedCheckDescription;
+
+            @Override
+            public void describeTo(final Description description) {
+                description.appendText(failedCheckDescription);
+            }
+
+            @Override
+            public boolean matchesSafely(final View view) {
+                Drawable background = view.getBackground();
+                try {
+                    TestUtils.assertAllPixelsOfColor("",
+                            background, view.getWidth(), view.getHeight(), true,
+                            fillColor, 0, true);
+                } catch (Throwable t) {
+                    failedCheckDescription = t.getMessage();
+                    return false;
+                }
+                return true;
+            }
+        };
+    }
+
+    /**
+     * Returns a matcher that matches {@link View}s based on the given parent type.
+     *
+     * @param parentMatcher the type of the parent to match on
+     */
+    public static Matcher<View> isChildOfA(final Matcher<View> parentMatcher) {
+        return new TypeSafeMatcher<View>() {
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("is child of a: ");
+                parentMatcher.describeTo(description);
+            }
+
+            @Override
+            public boolean matchesSafely(View view) {
+                final ViewParent viewParent = view.getParent();
+                if (!(viewParent instanceof View)) {
+                    return false;
+                }
+                if (parentMatcher.matches(viewParent)) {
+                    return true;
+                }
+                return false;
+            }
+        };
+    }
+
 }
diff --git a/design/tests/src/android/support/design/widget/BaseInstrumentationTestCase.java b/design/tests/src/android/support/design/widget/BaseInstrumentationTestCase.java
index bda213c..0c09e7b 100644
--- a/design/tests/src/android/support/design/widget/BaseInstrumentationTestCase.java
+++ b/design/tests/src/android/support/design/widget/BaseInstrumentationTestCase.java
@@ -17,26 +17,17 @@
 package android.support.design.widget;
 
 import android.app.Activity;
-import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
-import android.test.ActivityInstrumentationTestCase2;
-import org.junit.Before;
+import org.junit.Rule;
 import org.junit.runner.RunWith;
 
 @RunWith(AndroidJUnit4.class)
-public abstract class BaseInstrumentationTestCase<A extends Activity>
-        extends ActivityInstrumentationTestCase2<A> {
+public abstract class BaseInstrumentationTestCase<A extends Activity> {
+    @Rule
+    public final ActivityTestRule<A> mActivityTestRule;
 
     protected BaseInstrumentationTestCase(Class<A> activityClass) {
-        super(activityClass);
+        mActivityTestRule = new ActivityTestRule<A>(activityClass);
     }
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        injectInstrumentation(InstrumentationRegistry.getInstrumentation());
-        getActivity();
-    }
-
 }
diff --git a/design/tests/src/android/support/design/widget/BottomSheetBehaviorTest.java b/design/tests/src/android/support/design/widget/BottomSheetBehaviorTest.java
index 65da332..3d21033 100644
--- a/design/tests/src/android/support/design/widget/BottomSheetBehaviorTest.java
+++ b/design/tests/src/android/support/design/widget/BottomSheetBehaviorTest.java
@@ -16,13 +16,12 @@
 
 package android.support.design.widget;
 
-import org.hamcrest.Matcher;
-import org.junit.Test;
-
 import android.support.annotation.NonNull;
 import android.support.design.test.R;
+import android.support.test.InstrumentationRegistry;
 import android.support.test.espresso.Espresso;
 import android.support.test.espresso.IdlingResource;
+import android.support.test.espresso.action.CoordinatesProvider;
 import android.support.test.espresso.action.GeneralLocation;
 import android.support.test.espresso.action.GeneralSwipeAction;
 import android.support.test.espresso.action.Press;
@@ -34,6 +33,8 @@
 import android.test.suitebuilder.annotation.SmallTest;
 import android.view.View;
 import android.view.ViewGroup;
+import org.hamcrest.Matcher;
+import org.junit.Test;
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.not;
@@ -127,8 +128,21 @@
     public void testSwipeDownToCollapse() {
         checkSetState(BottomSheetBehavior.STATE_EXPANDED, ViewMatchers.isDisplayed());
         Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
-                .perform(DesignViewActions.withCustomConstraints(ViewActions.swipeDown(),
-                        ViewMatchers.isDisplayingAtLeast(5)));
+                .perform(DesignViewActions.withCustomConstraints(new GeneralSwipeAction(
+                        Swipe.FAST, GeneralLocation.TOP_CENTER,
+                        // Manually calculate the ending coordinates to make sure that the bottom
+                        // sheet is collapsed, not hidden
+                        new CoordinatesProvider() {
+                            @Override
+                            public float[] calculateCoordinates(View view) {
+                                BottomSheetBehavior behavior = getBehavior();
+                                return new float[]{
+                                        // x: center of the bottom sheet
+                                        view.getWidth() / 2,
+                                        // y: just above the peek height
+                                        view.getHeight() - behavior.getPeekHeight()};
+                            }
+                        }, Press.FINGER), ViewMatchers.isDisplayingAtLeast(5)));
         // Avoid a deadlock (b/26160710)
         registerIdlingResourceCallback();
         try {
@@ -162,8 +176,12 @@
         Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
                 .perform(DesignViewActions.withCustomConstraints(
                         new GeneralSwipeAction(Swipe.FAST,
-                                GeneralLocation.VISIBLE_CENTER, GeneralLocation.TOP_CENTER,
-                                Press.FINGER),
+                                GeneralLocation.VISIBLE_CENTER, new CoordinatesProvider() {
+                            @Override
+                            public float[] calculateCoordinates(View view) {
+                                return new float[]{view.getWidth() / 2, 0};
+                            }
+                        }, Press.FINGER),
                         ViewMatchers.isDisplayingAtLeast(5)));
         // Avoid a deadlock (b/26160710)
         registerIdlingResourceCallback();
@@ -176,10 +194,46 @@
         }
     }
 
-    private void checkSetState(int state, Matcher<View> matcher) {
+    @Test
+    @MediumTest
+    public void testInvisible() {
+        // Make the bottomsheet invisible
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                getBottomSheet().setVisibility(View.INVISIBLE);
+                assertThat(getBehavior().getState(), is(BottomSheetBehavior.STATE_COLLAPSED));
+            }
+        });
+        // Swipe up as if to expand it
+        Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
+                .perform(DesignViewActions.withCustomConstraints(
+                        new GeneralSwipeAction(Swipe.FAST,
+                                GeneralLocation.VISIBLE_CENTER, new CoordinatesProvider() {
+                            @Override
+                            public float[] calculateCoordinates(View view) {
+                                return new float[]{view.getWidth() / 2, 0};
+                            }
+                        }, Press.FINGER),
+                        not(ViewMatchers.isDisplayed())));
+        // Check that the bottom sheet stays the same collapsed state
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                assertThat(getBehavior().getState(), is(BottomSheetBehavior.STATE_COLLAPSED));
+            }
+        });
+    }
+
+    private void checkSetState(final int state, Matcher<View> matcher) {
         registerIdlingResourceCallback();
         try {
-            getBehavior().setState(state);
+            InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+                @Override
+                public void run() {
+                    getBehavior().setState(state);
+                }
+            });
             Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
                     .check(ViewAssertions.matches(matcher));
             assertThat(getBehavior().getState(), is(state));
@@ -202,15 +256,15 @@
     }
 
     private ViewGroup getBottomSheet() {
-        return getActivity().mBottomSheet;
+        return mActivityTestRule.getActivity().mBottomSheet;
     }
 
     private BottomSheetBehavior getBehavior() {
-        return getActivity().mBehavior;
+        return mActivityTestRule.getActivity().mBehavior;
     }
 
     private CoordinatorLayout getCoordinatorLayout() {
-        return getActivity().mCoordinatorLayout;
+        return mActivityTestRule.getActivity().mCoordinatorLayout;
     }
 
 }
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/ViewInfoStoreTest.java b/design/tests/src/android/support/design/widget/NavigationViewActivity.java
similarity index 64%
copy from v7/recyclerview/tests/src/android/support/v7/widget/ViewInfoStoreTest.java
copy to design/tests/src/android/support/design/widget/NavigationViewActivity.java
index 4c78e3d..c3e810c 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/ViewInfoStoreTest.java
+++ b/design/tests/src/android/support/design/widget/NavigationViewActivity.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.
@@ -13,8 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.support.v7.widget;
+package android.support.design.widget;
 
-public class ViewInfoStoreTest {
+import android.support.design.test.R;
 
+public class NavigationViewActivity extends BaseTestActivity {
+    @Override
+    protected int getContentViewLayoutResId() {
+        return R.layout.design_navigation_view;
+    }
 }
diff --git a/design/tests/src/android/support/design/widget/NavigationViewTest.java b/design/tests/src/android/support/design/widget/NavigationViewTest.java
new file mode 100755
index 0000000..c5f2512
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/NavigationViewTest.java
@@ -0,0 +1,558 @@
+/*
+ * 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.graphics.drawable.GradientDrawable;
+import android.support.annotation.ColorInt;
+import android.support.annotation.IdRes;
+import android.support.design.test.R;
+import android.support.v4.content.res.ResourcesCompat;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.SwitchCompat;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import org.hamcrest.Matcher;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static android.support.design.testutils.DrawerLayoutActions.closeDrawer;
+import static android.support.design.testutils.DrawerLayoutActions.openDrawer;
+import static android.support.design.testutils.NavigationViewActions.*;
+import static android.support.design.testutils.TestUtilsMatchers.*;
+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.ViewMatchers.*;
+import static org.hamcrest.core.AllOf.allOf;
+import static org.junit.Assert.*;
+
+public class NavigationViewTest
+        extends BaseInstrumentationTestCase<NavigationViewActivity> {
+    private static final int[] MENU_CONTENT_ITEM_IDS = { R.id.destination_home,
+            R.id.destination_profile, R.id.destination_people, R.id.destination_settings };
+    private Map<Integer, String> mMenuStringContent;
+
+    private DrawerLayout mDrawerLayout;
+
+    private NavigationView mNavigationView;
+
+    private int mLastSelectedNavigationItemId;
+
+    public NavigationViewTest() {
+        super(NavigationViewActivity.class);
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        final NavigationViewActivity activity = mActivityTestRule.getActivity();
+        mDrawerLayout = (DrawerLayout) activity.findViewById(R.id.drawer_layout);
+        mNavigationView = (NavigationView) mDrawerLayout.findViewById(R.id.start_drawer);
+
+        // Close the drawer to reset the state for the next test
+        onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START));
+
+        final Resources res = activity.getResources();
+        mMenuStringContent = new HashMap<>(MENU_CONTENT_ITEM_IDS.length);
+        mMenuStringContent.put(R.id.destination_home, res.getString(R.string.navigate_home));
+        mMenuStringContent.put(R.id.destination_profile, res.getString(R.string.navigate_profile));
+        mMenuStringContent.put(R.id.destination_people, res.getString(R.string.navigate_people));
+        mMenuStringContent.put(R.id.destination_settings,
+                res.getString(R.string.navigate_settings));
+    }
+
+    @Test
+    @SmallTest
+    public void testBasics() {
+        // Open our drawer
+        onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
+
+        // Check the contents of the Menu object
+        final Menu menu = mNavigationView.getMenu();
+        assertNotNull("Menu should not be null", menu);
+        assertEquals("Should have matching number of items", MENU_CONTENT_ITEM_IDS.length,
+                menu.size());
+        for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) {
+            final MenuItem currItem = menu.getItem(i);
+            assertEquals("ID for Item #" + i, MENU_CONTENT_ITEM_IDS[i], currItem.getItemId());
+        }
+
+        // Check that we have the expected menu items in our NavigationView
+        for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) {
+            onView(allOf(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i])),
+                    isDescendantOfA(withId(R.id.start_drawer)))).check(matches(isDisplayed()));
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testTextAppearance() {
+        // Open our drawer
+        onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
+
+        final Resources res = mActivityTestRule.getActivity().getResources();
+        final int defaultTextSize = res.getDimensionPixelSize(R.dimen.text_medium_size);
+
+        // Check the default style of the menu items in our NavigationView
+        for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) {
+            onView(allOf(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i])),
+                    isDescendantOfA(withId(R.id.start_drawer)))).check(
+                    matches(withTextSize(defaultTextSize)));
+        }
+
+        // Set a new text appearance on our NavigationView
+        onView(withId(R.id.start_drawer)).perform(setItemTextAppearance(R.style.TextSmallStyle));
+
+        // And check that all the menu items have the new style
+        final int newTextSize = res.getDimensionPixelSize(R.dimen.text_small_size);
+        for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) {
+            onView(allOf(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i])),
+                    isDescendantOfA(withId(R.id.start_drawer)))).check(
+                    matches(withTextSize(newTextSize)));
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testTextColor() {
+        // Open our drawer
+        onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
+
+        final Resources res = mActivityTestRule.getActivity().getResources();
+        final @ColorInt int defaultTextColor = ResourcesCompat.getColor(res,
+                R.color.emerald_text, null);
+
+        // Check the default text color of the menu items in our NavigationView
+        for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) {
+            onView(allOf(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i])),
+                    isDescendantOfA(withId(R.id.start_drawer)))).check(
+                    matches(withTextColor(defaultTextColor)));
+        }
+
+        // Set a new text color on our NavigationView
+        onView(withId(R.id.start_drawer)).perform(setItemTextColor(
+                ResourcesCompat.getColorStateList(res, R.color.color_state_list_lilac, null)));
+
+        // And check that all the menu items have the new color
+        final @ColorInt int newTextColor = ResourcesCompat.getColor(res,
+                R.color.lilac_default, null);
+        for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) {
+            onView(allOf(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i])),
+                    isDescendantOfA(withId(R.id.start_drawer)))).check(
+                    matches(withTextColor(newTextColor)));
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testBackground() {
+        // Open our drawer
+        onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
+
+        final Resources res = mActivityTestRule.getActivity().getResources();
+        final @ColorInt int defaultFillColor = ResourcesCompat.getColor(res,
+                R.color.sand_default, null);
+
+        // Check the default fill color of the menu items in our NavigationView
+        for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) {
+            // Note that here we're tying ourselves to the implementation details of the
+            // internal structure of the NavigationView. Specifically, we're looking at the
+            // direct child of RecyclerView which is expected to have the background set
+            // on it. If the internal implementation of NavigationView changes, the second
+            // Matcher below will need to be tweaked.
+            Matcher menuItemMatcher = allOf(
+                    hasDescendant(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i]))),
+                    isChildOfA(isAssignableFrom(RecyclerView.class)),
+                    isDescendantOfA(withId(R.id.start_drawer)));
+
+            onView(menuItemMatcher).check(matches(withBackgroundFill(defaultFillColor)));
+        }
+
+        // Set a new background (flat fill color) on our NavigationView
+        onView(withId(R.id.start_drawer)).perform(setItemBackgroundResource(
+                R.drawable.test_background_blue));
+
+        // And check that all the menu items have the new fill
+        final @ColorInt int newFillColorBlue = ResourcesCompat.getColor(res,
+                R.color.test_blue, null);
+        for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) {
+            Matcher menuItemMatcher = allOf(
+                    hasDescendant(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i]))),
+                    isChildOfA(isAssignableFrom(RecyclerView.class)),
+                    isDescendantOfA(withId(R.id.start_drawer)));
+
+            onView(menuItemMatcher).check(matches(withBackgroundFill(newFillColorBlue)));
+        }
+
+        // Set another new background on our NavigationView
+        onView(withId(R.id.start_drawer)).perform(setItemBackground(
+                ResourcesCompat.getDrawable(res, R.drawable.test_background_green, null)));
+
+        // And check that all the menu items have the new fill
+        final @ColorInt int newFillColorGreen = ResourcesCompat.getColor(res,
+                R.color.test_green, null);
+        for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) {
+            Matcher menuItemMatcher = allOf(
+                    hasDescendant(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i]))),
+                    isChildOfA(isAssignableFrom(RecyclerView.class)),
+                    isDescendantOfA(withId(R.id.start_drawer)));
+
+            onView(menuItemMatcher).check(matches(withBackgroundFill(newFillColorGreen)));
+        }
+    }
+
+    /**
+     * Custom drawable class that provides a reliable way for testing various tinting scenarios
+     * across a range of platform versions. ColorDrawable doesn't support tinting on Kitkat and
+     * below, and BitmapDrawable (PNG sources) appears to slightly alter green and blue channels
+     * by a few units on some of the older platform versions (Gingerbread). Using GradientDrawable
+     * allows doing reliable tests at the level of individual channels (alpha / red / green / blue)
+     * for tinted and untinted icons in the testIconTinting method.
+     */
+    private class TestDrawable extends GradientDrawable {
+        private int mWidth;
+        private int mHeight;
+
+        public TestDrawable(@ColorInt int color, int width, int height) {
+            super(Orientation.TOP_BOTTOM, new int[] { color, color });
+            mWidth = width;
+            mHeight = height;
+        }
+
+        @Override
+        public int getIntrinsicWidth() {
+            return mWidth;
+        }
+
+        @Override
+        public int getIntrinsicHeight() {
+            return mHeight;
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testIconTinting() {
+        // Open our drawer
+        onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
+
+        final Resources res = mActivityTestRule.getActivity().getResources();
+        final @ColorInt int redFill = ResourcesCompat.getColor(res, R.color.test_red, null);
+        final @ColorInt int greenFill = ResourcesCompat.getColor(res, R.color.test_green, null);
+        final @ColorInt int blueFill = ResourcesCompat.getColor(res, R.color.test_blue, null);
+        final int iconSize = res.getDimensionPixelSize(R.dimen.drawable_small_size);
+        onView(withId(R.id.start_drawer)).perform(setIconForMenuItem(R.id.destination_home,
+                new TestDrawable(redFill, iconSize, iconSize)));
+        onView(withId(R.id.start_drawer)).perform(setIconForMenuItem(R.id.destination_profile,
+                new TestDrawable(greenFill, iconSize, iconSize)));
+        onView(withId(R.id.start_drawer)).perform(setIconForMenuItem(R.id.destination_people,
+                new TestDrawable(blueFill, iconSize, iconSize)));
+
+        final @ColorInt int defaultTintColor = ResourcesCompat.getColor(res,
+                R.color.emerald_translucent, null);
+
+        // We're allowing a margin of error in checking the color of the items' icons.
+        // This is due to the translucent color being used in the icon tinting
+        // and off-by-one discrepancies of SRC_IN when it's compositing
+        // translucent color. Note that all the checks below are written for the current
+        // logic on NavigationView that uses the default SRC_IN tint mode - effectively
+        // replacing all non-transparent pixels in the destination (original icon) with
+        // our translucent tint color.
+        final int allowedComponentVariance = 1;
+
+        // Note that here we're tying ourselves to the implementation details of the
+        // internal structure of the NavigationView. Specifically, we're checking the
+        // start drawable of the text view with the specific text. If the internal
+        // implementation of NavigationView changes, the second Matcher in the lookups
+        // below will need to be tweaked.
+        onView(allOf(withText(mMenuStringContent.get(R.id.destination_home)),
+                isDescendantOfA(withId(R.id.start_drawer)))).check(matches(
+                    withStartDrawableFilledWith(defaultTintColor, allowedComponentVariance)));
+        onView(allOf(withText(mMenuStringContent.get(R.id.destination_profile)),
+                isDescendantOfA(withId(R.id.start_drawer)))).check(matches(
+                    withStartDrawableFilledWith(defaultTintColor, allowedComponentVariance)));
+        onView(allOf(withText(mMenuStringContent.get(R.id.destination_people)),
+                isDescendantOfA(withId(R.id.start_drawer)))).check(matches(
+                    withStartDrawableFilledWith(defaultTintColor, allowedComponentVariance)));
+
+        final @ColorInt int newTintColor = ResourcesCompat.getColor(res,
+                R.color.red_translucent, null);
+
+        onView(withId(R.id.start_drawer)).perform(setItemIconTintList(
+                ResourcesCompat.getColorStateList(res, R.color.color_state_list_red_translucent,
+                        null)));
+        // Check that all menu items with icons now have icons tinted with the newly set color
+        onView(allOf(withText(mMenuStringContent.get(R.id.destination_home)),
+                isDescendantOfA(withId(R.id.start_drawer)))).check(matches(
+                    withStartDrawableFilledWith(newTintColor, allowedComponentVariance)));
+        onView(allOf(withText(mMenuStringContent.get(R.id.destination_profile)),
+                isDescendantOfA(withId(R.id.start_drawer)))).check(matches(
+                    withStartDrawableFilledWith(newTintColor, allowedComponentVariance)));
+        onView(allOf(withText(mMenuStringContent.get(R.id.destination_people)),
+                isDescendantOfA(withId(R.id.start_drawer)))).check(matches(
+                    withStartDrawableFilledWith(newTintColor, allowedComponentVariance)));
+
+        // And now remove all icon tinting
+        onView(withId(R.id.start_drawer)).perform(setItemIconTintList(null));
+        // And verify that all menu items with icons now have the original colors for their icons.
+        // Note that since there is no tinting at this point, we don't allow any color variance
+        // in these checks.
+        onView(allOf(withText(mMenuStringContent.get(R.id.destination_home)),
+                isDescendantOfA(withId(R.id.start_drawer)))).check(matches(
+                    withStartDrawableFilledWith(redFill, 0)));
+        onView(allOf(withText(mMenuStringContent.get(R.id.destination_profile)),
+                isDescendantOfA(withId(R.id.start_drawer)))).check(matches(
+                    withStartDrawableFilledWith(greenFill, 0)));
+        onView(allOf(withText(mMenuStringContent.get(R.id.destination_people)),
+                isDescendantOfA(withId(R.id.start_drawer)))).check(matches(
+                    withStartDrawableFilledWith(blueFill, 0)));
+    }
+
+    /**
+     * Gets the list of header IDs (which can be empty) and verifies that the actual header content
+     * of our navigation view matches the expected header content.
+     */
+    private void verifyHeaders(@IdRes int ... expectedHeaderIds) {
+        final int expectedHeaderCount = (expectedHeaderIds != null) ? expectedHeaderIds.length : 0;
+        final int actualHeaderCount = mNavigationView.getHeaderCount();
+        assertEquals("Header count", expectedHeaderCount, actualHeaderCount);
+
+        if (expectedHeaderCount > 0) {
+            for (int i = 0; i < expectedHeaderCount; i++) {
+                final View currentHeader = mNavigationView.getHeaderView(i);
+                assertEquals("Header at #" + i, expectedHeaderIds[i], currentHeader.getId());
+            }
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testHeaders() {
+        // Open our drawer
+        onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
+
+        // We should have no headers at the start
+        verifyHeaders();
+
+        // Inflate one header and check that it's there in the navigation view
+        onView(withId(R.id.start_drawer)).perform(
+                inflateHeaderView(R.layout.design_navigation_view_header1));
+        verifyHeaders(R.id.header1);
+
+        final LayoutInflater inflater = LayoutInflater.from(mActivityTestRule.getActivity());
+
+        // Add one more header and check that it's there in the navigation view
+        onView(withId(R.id.start_drawer)).perform(
+                addHeaderView(inflater, R.layout.design_navigation_view_header2));
+        verifyHeaders(R.id.header1, R.id.header2);
+
+        final View header1 = mNavigationView.findViewById(R.id.header1);
+        // Remove the first header and check that we still have the second header
+        onView(withId(R.id.start_drawer)).perform(removeHeaderView(header1));
+        verifyHeaders(R.id.header2);
+
+        // Add one more header and check that we now have two headers
+        onView(withId(R.id.start_drawer)).perform(
+                inflateHeaderView(R.layout.design_navigation_view_header3));
+        verifyHeaders(R.id.header2, R.id.header3);
+
+        // Add another "copy" of the header from the just-added layout and check that we now
+        // have three headers
+        onView(withId(R.id.start_drawer)).perform(
+                addHeaderView(inflater, R.layout.design_navigation_view_header3));
+        verifyHeaders(R.id.header2, R.id.header3, R.id.header3);
+    }
+
+    @Test
+    @SmallTest
+    public void testNavigationSelectionListener() {
+        // Open our drawer
+        onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
+
+        // Click one of our items
+        onView(allOf(withText(mMenuStringContent.get(R.id.destination_people)),
+                isDescendantOfA(withId(R.id.start_drawer)))).perform(click());
+        // Check that the drawer is still open
+        assertTrue("Drawer is still open after click",
+                mDrawerLayout.isDrawerOpen(GravityCompat.START));
+
+        // Register a listener
+        mNavigationView.setNavigationItemSelectedListener(
+                new NavigationView.OnNavigationItemSelectedListener() {
+                    @Override
+                    public boolean onNavigationItemSelected(MenuItem item) {
+                        mLastSelectedNavigationItemId = item.getItemId();
+                        return false;
+                    }
+                });
+
+        // Click one of our items
+        onView(allOf(withText(mMenuStringContent.get(R.id.destination_profile)),
+                isDescendantOfA(withId(R.id.start_drawer)))).perform(click());
+        // Check that the drawer is still open
+        assertTrue("Drawer is still open after click",
+                mDrawerLayout.isDrawerOpen(GravityCompat.START));
+        // And that our listener has been notified of the click
+        assertEquals("Selected item ID", R.id.destination_profile, mLastSelectedNavigationItemId);
+
+        // Reset the tracker field and set null listener
+        mLastSelectedNavigationItemId = -1;
+        mNavigationView.setNavigationItemSelectedListener(null);
+
+        // Click one of our items
+        onView(allOf(withText(mMenuStringContent.get(R.id.destination_settings)),
+                isDescendantOfA(withId(R.id.start_drawer)))).perform(click());
+        // Check that the drawer is still open
+        assertTrue("Drawer is still open after click",
+                mDrawerLayout.isDrawerOpen(GravityCompat.START));
+        // And that our previous listener has not been notified of the click
+        assertEquals("Selected item ID", -1, mLastSelectedNavigationItemId);
+    }
+
+    private void verifyCheckedAppearance(@IdRes int checkedItemId,
+            @ColorInt int uncheckedItemForeground, @ColorInt int checkedItemForeground,
+            @ColorInt int uncheckedItemBackground, @ColorInt int checkedItemBackground) {
+        for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) {
+            final boolean expectedToBeChecked = (MENU_CONTENT_ITEM_IDS[i] == checkedItemId);
+            final @ColorInt int expectedItemForeground =
+                    expectedToBeChecked ? checkedItemForeground : uncheckedItemForeground;
+            final @ColorInt int expectedItemBackground =
+                    expectedToBeChecked ? checkedItemBackground : uncheckedItemBackground;
+
+            // For the background fill check we need to select a view that has its background
+            // set by the current implementation (see disclaimer in testBackground)
+            Matcher menuItemMatcher = allOf(
+                    hasDescendant(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i]))),
+                    isChildOfA(isAssignableFrom(RecyclerView.class)),
+                    isDescendantOfA(withId(R.id.start_drawer)));
+            onView(menuItemMatcher).check(matches(withBackgroundFill(expectedItemBackground)));
+
+            // And for the foreground color check we need to select a view with the text content
+            Matcher menuItemTextMatcher = allOf(
+                    withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i])),
+                    isDescendantOfA(withId(R.id.start_drawer)));
+            onView(menuItemTextMatcher).check(matches(withTextColor(expectedItemForeground)));
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testCheckedAppearance() {
+        // Open our drawer
+        onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
+
+        // Reconfigure our navigation view to use foreground (text) and background visuals
+        // with explicitly different colors for the checked state
+        final Resources res = mActivityTestRule.getActivity().getResources();
+        onView(withId(R.id.start_drawer)).perform(setItemTextColor(
+                ResourcesCompat.getColorStateList(res, R.color.color_state_list_sand, null)));
+        onView(withId(R.id.start_drawer)).perform(setItemBackgroundResource(
+                R.drawable.test_drawable_state_list));
+
+        final @ColorInt int uncheckedItemForeground = ResourcesCompat.getColor(res,
+                R.color.sand_default, null);
+        final @ColorInt int checkedItemForeground = ResourcesCompat.getColor(res,
+                R.color.sand_checked, null);
+        final @ColorInt int uncheckedItemBackground = ResourcesCompat.getColor(res,
+                R.color.test_green, null);
+        final @ColorInt int checkedItemBackground = ResourcesCompat.getColor(res,
+                R.color.test_blue, null);
+
+        // Verify that all items are rendered with unchecked visuals
+        verifyCheckedAppearance(0, uncheckedItemForeground, checkedItemForeground,
+                uncheckedItemBackground, checkedItemBackground);
+
+        // Mark one of the items as checked
+        onView(withId(R.id.start_drawer)).perform(setCheckedItem(R.id.destination_profile));
+        // And verify that it's now rendered with checked visuals
+        verifyCheckedAppearance(R.id.destination_profile,
+                uncheckedItemForeground, checkedItemForeground,
+                uncheckedItemBackground, checkedItemBackground);
+
+        // Register a navigation listener that "marks" the selected item
+        mNavigationView.setNavigationItemSelectedListener(
+                new NavigationView.OnNavigationItemSelectedListener() {
+                    @Override
+                    public boolean onNavigationItemSelected(MenuItem item) {
+                        return true;
+                    }
+                });
+
+        // Click one of our items
+        onView(allOf(withText(mMenuStringContent.get(R.id.destination_people)),
+                isDescendantOfA(withId(R.id.start_drawer)))).perform(click());
+        // and verify that it's now checked
+        verifyCheckedAppearance(R.id.destination_people,
+                uncheckedItemForeground, checkedItemForeground,
+                uncheckedItemBackground, checkedItemBackground);
+
+        // Register a navigation listener that doesn't "mark" the selected item
+        mNavigationView.setNavigationItemSelectedListener(
+                new NavigationView.OnNavigationItemSelectedListener() {
+                    @Override
+                    public boolean onNavigationItemSelected(MenuItem item) {
+                        return false;
+                    }
+                });
+
+        // Click another items
+        onView(allOf(withText(mMenuStringContent.get(R.id.destination_settings)),
+                isDescendantOfA(withId(R.id.start_drawer)))).perform(click());
+        // and verify that the checked state remains on the previously clicked item
+        // since the current navigation listener returns false from its callback
+        // implementation
+        verifyCheckedAppearance(R.id.destination_people,
+                uncheckedItemForeground, checkedItemForeground,
+                uncheckedItemBackground, checkedItemBackground);
+    }
+
+    @Test
+    @SmallTest
+    public void testActionLayout() {
+        // Open our drawer
+        onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
+
+        // There are four conditions to "find" the menu item with action layout (switch):
+        // 1. Is in the NavigationView
+        // 2. Is direct child of a class that extends RecyclerView
+        // 3. Has a child with "people" text
+        // 4. Has fully displayed child that extends SwitchCompat
+        // Note that condition 2 makes a certain assumption about the internal implementation
+        // details of the NavigationMenu, while conditions 3 and 4 aim to be as generic as
+        // possible and to not rely on the internal details of the current layout implementation
+        // of an individual menu item in NavigationMenu.
+        Matcher menuItemMatcher = allOf(
+                isDescendantOfA(withId(R.id.start_drawer)),
+                isChildOfA(isAssignableFrom(RecyclerView.class)),
+                hasDescendant(withText(mMenuStringContent.get(R.id.destination_people))),
+                hasDescendant(allOf(
+                        isAssignableFrom(SwitchCompat.class),
+                        isCompletelyDisplayed())));
+
+        // While we don't need to perform any action on our row, the invocation of perform()
+        // makes our matcher actually run. If for some reason NavigationView fails to inflate and
+        // display our SwitchCompat action layout, the next line will fail in the matcher pass.
+        onView(menuItemMatcher).perform(click());
+    }
+}
diff --git a/design/tests/src/android/support/design/widget/SnackbarBucketTests.java b/design/tests/src/android/support/design/widget/SnackbarBucketTests.java
index e4b1b68..db8d33a 100644
--- a/design/tests/src/android/support/design/widget/SnackbarBucketTests.java
+++ b/design/tests/src/android/support/design/widget/SnackbarBucketTests.java
@@ -16,9 +16,6 @@
 
 package android.support.design.widget;
 
-import org.junit.Test;
-
-import android.content.Intent;
 import android.os.SystemClock;
 import android.support.design.test.R;
 import android.support.test.espresso.ViewAction;
@@ -26,6 +23,7 @@
 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;
@@ -33,6 +31,8 @@
 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> {
 
@@ -121,6 +121,6 @@
     }
 
     private CoordinatorLayout getCoordinatorLayout() {
-        return getActivity().mCoordinatorLayout;
+        return mActivityTestRule.getActivity().mCoordinatorLayout;
     }
 }
diff --git a/design/tests/src/android/support/design/widget/TabLayoutWithLayoutItems.java b/design/tests/src/android/support/design/widget/TabLayoutWithLayoutItems.java
index 61e5768..603c2d4 100755
--- a/design/tests/src/android/support/design/widget/TabLayoutWithLayoutItems.java
+++ b/design/tests/src/android/support/design/widget/TabLayoutWithLayoutItems.java
@@ -16,12 +16,14 @@
 
 package android.support.design.widget;
 
-import org.junit.Test;
-
 import android.support.design.test.R;
+import android.support.test.InstrumentationRegistry;
 import android.support.v7.app.AppCompatActivity;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.view.LayoutInflater;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
 
 public class TabLayoutWithLayoutItems extends BaseInstrumentationTestCase<AppCompatActivity> {
 
@@ -31,11 +33,12 @@
 
     @Test
     @SmallTest
-    public void testInflateTabLayoutWithTabItems() throws Throwable {
-        runTestOnUiThread(new Runnable() {
+    public void testInflateTabLayoutWithTabItems() {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
             @Override
             public void run() {
-                final LayoutInflater inflater = LayoutInflater.from(getActivity());
+                final LayoutInflater inflater =
+                        LayoutInflater.from(mActivityTestRule.getActivity());
                 final TabLayout tabLayout = (TabLayout) inflater.inflate(
                         R.layout.design_tabs_items, null);
 
@@ -43,7 +46,8 @@
 
                 // Tab 0 has text, but no icon or custom view
                 TabLayout.Tab tab = tabLayout.getTabAt(0);
-                assertEquals(getActivity().getString(R.string.tab_layout_text), tab.getText());
+                assertEquals(mActivityTestRule.getActivity().getString(R.string.tab_layout_text),
+                        tab.getText());
                 assertNull(tab.getIcon());
                 assertNull(tab.getCustomView());
 
@@ -66,12 +70,20 @@
     @Test(expected = IllegalArgumentException.class)
     @SmallTest
     public void testInflateTabLayoutWithNonTabItem() throws Throwable {
-        runTestOnUiThread(new Runnable() {
-            @Override
+        final Throwable[] exceptions = new Throwable[1];
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
             public void run() {
-                final LayoutInflater inflater = LayoutInflater.from(getActivity());
-                inflater.inflate(R.layout.design_tabs_with_non_tabitems, null);
+                try {
+                    final LayoutInflater inflater =
+                            LayoutInflater.from(mActivityTestRule.getActivity());
+                    inflater.inflate(R.layout.design_tabs_with_non_tabitems, null);
+                } catch (Throwable throwable) {
+                    exceptions[0] = throwable;
+                }
             }
         });
+        if (exceptions[0] != null) {
+            throw exceptions[0];
+        }
     }
 }
diff --git a/design/tests/src/android/support/design/widget/TabLayoutWithViewPagerActivity.java b/design/tests/src/android/support/design/widget/TabLayoutWithViewPagerActivity.java
index 6fe99a5..ff92b36 100644
--- a/design/tests/src/android/support/design/widget/TabLayoutWithViewPagerActivity.java
+++ b/design/tests/src/android/support/design/widget/TabLayoutWithViewPagerActivity.java
@@ -1,3 +1,18 @@
+/*
+ * 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;
diff --git a/design/tests/src/android/support/design/widget/TabLayoutWithViewPagerTest.java b/design/tests/src/android/support/design/widget/TabLayoutWithViewPagerTest.java
index 0a86bce..15d3780 100755
--- a/design/tests/src/android/support/design/widget/TabLayoutWithViewPagerTest.java
+++ b/design/tests/src/android/support/design/widget/TabLayoutWithViewPagerTest.java
@@ -34,6 +34,7 @@
 import android.widget.HorizontalScrollView;
 import android.widget.TextView;
 import org.hamcrest.Matcher;
+import org.junit.Before;
 import org.junit.Test;
 
 import java.util.ArrayList;
@@ -43,6 +44,7 @@
 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;
 
 public class TabLayoutWithViewPagerTest
         extends BaseInstrumentationTestCase<TabLayoutWithViewPagerActivity> {
@@ -165,10 +167,9 @@
         super(TabLayoutWithViewPagerActivity.class);
     }
 
+    @Before
     public void setUp() throws Exception {
-        super.setUp();
-
-        final TabLayoutWithViewPagerActivity activity = getActivity();
+        final TabLayoutWithViewPagerActivity activity = mActivityTestRule.getActivity();
         mTabLayout = (TabLayout) activity.findViewById(R.id.tabs);
         mViewPager = (ViewPager) activity.findViewById(R.id.tabs_viewpager);
 
@@ -395,7 +396,7 @@
             @DimenRes int tabMaxWidthResId) {
         assertEquals("Scrollable tab mode", TabLayout.MODE_SCROLLABLE, mTabLayout.getTabMode());
 
-        final Resources res = getActivity().getResources();
+        final Resources res = mActivityTestRule.getActivity().getResources();
         final int minTabWidth = (tabMinWidthResId == 0) ? -1 :
                 res.getDimensionPixelSize(tabMinWidthResId);
         final int maxTabWidth = (tabMaxWidthResId == 0) ? -1 :
diff --git a/graphics/drawable/animated/build.gradle b/graphics/drawable/animated/build.gradle
index 6a55389..beed037 100644
--- a/graphics/drawable/animated/build.gradle
+++ b/graphics/drawable/animated/build.gradle
@@ -1,4 +1,4 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
 
 archivesBaseName = 'animated-vector-drawable'
 
@@ -23,6 +23,11 @@
         androidTest.manifest.srcFile 'tests/AndroidManifest.xml'
     }
 
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_7
+        targetCompatibility JavaVersion.VERSION_1_7
+    }
+
     lintOptions {
         abortOnError true
     }
diff --git a/graphics/drawable/static/build.gradle b/graphics/drawable/static/build.gradle
index 5561d0d..f0a363f 100644
--- a/graphics/drawable/static/build.gradle
+++ b/graphics/drawable/static/build.gradle
@@ -1,4 +1,4 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
 
 archivesBaseName = 'support-vector-drawable'
 
@@ -26,6 +26,11 @@
         androidTest.manifest.srcFile 'tests/AndroidManifest.xml'
     }
 
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_7
+        targetCompatibility JavaVersion.VERSION_1_7
+    }
+
     lintOptions {
         abortOnError true
     }
diff --git a/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_arcto_golden.png b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_arcto_golden.png
deleted file mode 100644
index f16e603..0000000
--- a/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_arcto_golden.png
+++ /dev/null
Binary files differ
diff --git a/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_implicit_lineto_golden.png b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_implicit_lineto_golden.png
deleted file mode 100644
index 94ac7f0..0000000
--- a/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_implicit_lineto_golden.png
+++ /dev/null
Binary files differ
diff --git a/graphics/drawable/static/tests/res/drawable/vector_icon_arcto.xml b/graphics/drawable/static/tests/res/drawable/vector_icon_arcto.xml
deleted file mode 100644
index a4904f2..0000000
--- a/graphics/drawable/static/tests/res/drawable/vector_icon_arcto.xml
+++ /dev/null
@@ -1,24 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="20dp"
-        android:height="50dp"
-        android:viewportWidth="20.0"
-        android:viewportHeight="50.0">
-    <path
-            android:pathData="M14.285706,47.362198A50.71429,62.14286 0,0 0,1.0630035 5.5146027"
-            android:fillColor="#ff55ff"/>
-</vector>
\ No newline at end of file
diff --git a/graphics/drawable/static/tests/res/drawable/vector_icon_implicit_lineto.xml b/graphics/drawable/static/tests/res/drawable/vector_icon_implicit_lineto.xml
deleted file mode 100644
index 41b3e1c..0000000
--- a/graphics/drawable/static/tests/res/drawable/vector_icon_implicit_lineto.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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:height="64dp"
-        android:width="64dp"
-        android:viewportHeight="64"
-        android:viewportWidth="64">
-
-    <path
-            android:fillColor="#FF000000"
-            android:pathData="m0,0 32,0 0,32 -32,0 0,-32z"/>
-
-</vector>
\ No newline at end of file
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 4163eb2..85fd597 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
@@ -64,8 +64,6 @@
             R.drawable.vector_icon_stroke_3,
             R.drawable.vector_icon_scale_1,
             R.drawable.vector_icon_scale_2,
-            R.drawable.vector_icon_implicit_lineto,
-            R.drawable.vector_icon_arcto,
     };
 
     private static final int[] GOLDEN_IMAGES = new int[]{
@@ -94,8 +92,6 @@
             R.drawable.vector_icon_stroke_3_golden,
             R.drawable.vector_icon_scale_1_golden,
             R.drawable.vector_icon_scale_2_golden,
-            R.drawable.vector_icon_implicit_lineto_golden,
-            R.drawable.vector_icon_arcto_golden,
     };
 
     private static final int TEST_ICON = R.drawable.vector_icon_create;
@@ -288,7 +284,7 @@
 
     public void testGetConstantState() {
         VectorDrawableCompat vectorDrawable =
-                VectorDrawableCompat.create(mResources, R.drawable.vector_icon_arcto, mTheme);
+                VectorDrawableCompat.create(mResources, R.drawable.vector_icon_delete, mTheme);
         Drawable.ConstantState constantState = vectorDrawable.getConstantState();
         assertNotNull(constantState);
         assertEquals(0, constantState.getChangingConfigurations());
diff --git a/percent/build.gradle b/percent/build.gradle
index dc8a9a8..0603ad9 100644
--- a/percent/build.gradle
+++ b/percent/build.gradle
@@ -1,4 +1,4 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
 
 archivesBaseName = 'percent'
 
@@ -15,7 +15,7 @@
 }
 
 android {
-    compileSdkVersion 'current'
+    compileSdkVersion project.ext.currentSdk
 
     defaultConfig {
         minSdkVersion 8
diff --git a/percent/src/android/support/percent/PercentLayoutHelper.java b/percent/src/android/support/percent/PercentLayoutHelper.java
index a76eed9..956b428 100644
--- a/percent/src/android/support/percent/PercentLayoutHelper.java
+++ b/percent/src/android/support/percent/PercentLayoutHelper.java
@@ -319,6 +319,20 @@
                 info.mPreservedParams.height == ViewGroup.LayoutParams.WRAP_CONTENT;
     }
 
+    /* package */ static class PercentMarginLayoutParams extends ViewGroup.MarginLayoutParams {
+        // These two flags keep track of whether we're computing the LayoutParams width and height
+        // in the fill pass based on the aspect ratio. This allows the fill pass to be re-entrant
+        // as the framework code can call onMeasure() multiple times before the onLayout() is
+        // called. Those multiple invocations of onMeasure() are not guaranteed to be called with
+        // the same set of width / height.
+        private boolean mIsHeightComputedFromAspectRatio;
+        private boolean mIsWidthComputedFromAspectRatio;
+
+        public PercentMarginLayoutParams(int width, int height) {
+            super(width, height);
+        }
+    }
+
     /**
      * Container for information about percentage dimensions and margins. It acts as an extension
      * for {@code LayoutParams}.
@@ -342,7 +356,7 @@
 
         public float aspectRatio;
 
-        /* package */ final ViewGroup.MarginLayoutParams mPreservedParams;
+        /* package */ final PercentMarginLayoutParams mPreservedParams;
 
         public PercentLayoutInfo() {
             widthPercent = -1f;
@@ -353,7 +367,7 @@
             bottomMarginPercent = -1f;
             startMarginPercent = -1f;
             endMarginPercent = -1f;
-            mPreservedParams = new ViewGroup.MarginLayoutParams(0, 0);
+            mPreservedParams = new PercentMarginLayoutParams(0, 0);
         }
 
         /**
@@ -369,8 +383,12 @@
             // necessarily be true, as the user might explicitly set it to 0. However, we use this
             // information only for the aspect ratio. If the user set the aspect ratio attribute,
             // it means they accept or soon discover that it will be disregarded.
-            final boolean widthNotSet = params.width == 0 && widthPercent < 0;
-            final boolean heightNotSet = params.height == 0 && heightPercent < 0;
+            final boolean widthNotSet =
+                    (mPreservedParams.mIsWidthComputedFromAspectRatio
+                            || mPreservedParams.width == 0) && (widthPercent < 0);
+            final boolean heightNotSet =
+                    (mPreservedParams.mIsHeightComputedFromAspectRatio
+                            || mPreservedParams.height == 0) && (heightPercent < 0);
 
             if (widthPercent >= 0) {
                 params.width = (int) (widthHint * widthPercent);
@@ -383,9 +401,13 @@
             if (aspectRatio >= 0) {
                 if (widthNotSet) {
                     params.width = (int) (params.height * aspectRatio);
+                    // Keep track that we've filled the width based on the height and aspect ratio.
+                    mPreservedParams.mIsWidthComputedFromAspectRatio = true;
                 }
                 if (heightNotSet) {
                     params.height = (int) (params.width / aspectRatio);
+                    // Keep track that we've filled the height based on the width and aspect ratio.
+                    mPreservedParams.mIsHeightComputedFromAspectRatio = true;
                 }
             }
 
@@ -490,8 +512,20 @@
          * {@link PercentLayoutHelper.PercentLayoutInfo#fillLayoutParams}.
          */
         public void restoreLayoutParams(ViewGroup.LayoutParams params) {
-            params.width = mPreservedParams.width;
-            params.height = mPreservedParams.height;
+            if (!mPreservedParams.mIsWidthComputedFromAspectRatio) {
+                // Only restore the width if we didn't compute it based on the height and
+                // aspect ratio in the fill pass.
+                params.width = mPreservedParams.width;
+            }
+            if (!mPreservedParams.mIsHeightComputedFromAspectRatio) {
+                // Only restore the height if we didn't compute it based on the width and
+                // aspect ratio in the fill pass.
+                params.height = mPreservedParams.height;
+            }
+
+            // Reset the tracking flags.
+            mPreservedParams.mIsWidthComputedFromAspectRatio = false;
+            mPreservedParams.mIsHeightComputedFromAspectRatio = false;
         }
     }
 
diff --git a/percent/tests/AndroidManifest.xml b/percent/tests/AndroidManifest.xml
index 224187a..c4cc597 100644
--- a/percent/tests/AndroidManifest.xml
+++ b/percent/tests/AndroidManifest.xml
@@ -27,6 +27,8 @@
         <uses-library android:name="android.test.runner" />
 
         <activity android:name="android.support.percent.TestFrameActivity"/>
+        <activity android:name="android.support.percent.TestRelativeActivity"/>
+        <activity android:name="android.support.percent.TestRelativeRtlActivity"/>
     </application>
 
     <instrumentation
diff --git a/percent/tests/java/android/support/percent/BaseInstrumentationTestCase.java b/percent/tests/java/android/support/percent/BaseInstrumentationTestCase.java
index 997820c..1fff432 100644
--- a/percent/tests/java/android/support/percent/BaseInstrumentationTestCase.java
+++ b/percent/tests/java/android/support/percent/BaseInstrumentationTestCase.java
@@ -17,26 +17,33 @@
 package android.support.percent;
 
 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.test.ActivityInstrumentationTestCase2;
+import junit.framework.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.runner.RunWith;
 
+import static junit.framework.Assert.assertEquals;
+
 @RunWith(AndroidJUnit4.class)
-public abstract class BaseInstrumentationTestCase<A extends Activity>
-        extends ActivityInstrumentationTestCase2<A> {
+public abstract class BaseInstrumentationTestCase<A extends Activity> {
+    @Rule
+    public final ActivityTestRule<A> mActivityTestRule;
 
     protected BaseInstrumentationTestCase(Class<A> activityClass) {
-        super(activityClass);
+        mActivityTestRule = new ActivityTestRule<A>(activityClass);
     }
 
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        injectInstrumentation(InstrumentationRegistry.getInstrumentation());
-        getActivity();
+    protected static void assertFuzzyEquals(String description, float expected, float actual) {
+        // On devices with certain screen densities we may run into situations where multiplying
+        // container width / height by a certain fraction ends up in a number that is almost but
+        // not exactly a round float number. For example, we can do float math to compute 15%
+        // of 1440 pixels and get 216.00002 due to inexactness of float math. This is why our
+        // tolerance is slightly bigger than 1 pixel in the comparison below.
+        assertEquals(description, expected, actual, 1.1f);
     }
-
 }
diff --git a/percent/tests/java/android/support/percent/PercentFrameTest.java b/percent/tests/java/android/support/percent/PercentFrameTest.java
index ff7bd6a..67d2ae5 100644
--- a/percent/tests/java/android/support/percent/PercentFrameTest.java
+++ b/percent/tests/java/android/support/percent/PercentFrameTest.java
@@ -27,6 +27,7 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import static android.support.percent.LayoutDirectionActions.setLayoutDirection;
 import static android.support.test.espresso.Espresso.onView;
 import static android.support.test.espresso.matcher.ViewMatchers.withId;
 
@@ -42,27 +43,12 @@
 
     @Before
     public void setUp() throws Exception {
-        super.setUp();
-
-        final TestFrameActivity activity = getActivity();
+        final TestFrameActivity activity = mActivityTestRule.getActivity();
         mPercentFrameLayout = (PercentFrameLayout) activity.findViewById(R.id.container);
         mContainerWidth = mPercentFrameLayout.getWidth();
         mContainerHeight = mPercentFrameLayout.getHeight();
     }
 
-    private void assertFuzzyEquals(String description, float expected, float actual) {
-        float difference = actual - expected;
-        // On devices with certain screen densities we may run into situations where multiplying
-        // container width / height by a certain fraction ends up in a number that is almost but
-        // not exactly a round float number. For example, we can do float math to compute 15%
-        // of 1440 pixels and get 216.00002 due to inexactness of float math. This is why our
-        // tolerance is slightly bigger than 1 pixel in the comparison below.
-        if (Math.abs(difference) > 1.1) {
-            Assert.fail(description + ": the difference between expected [" + expected +
-                    "] and actual [" + actual + "] is not within the tolerance bound");
-        }
-    }
-
     @Test
     @UiThreadTest
     public void testWidthHeight() {
@@ -175,7 +161,6 @@
         int childRight = childToTest.getRight();
         int childBottom = childToTest.getBottom();
 
-        //Debug.waitForDebugger();
         assertFuzzyEquals("Child width as 60% of the container",
                 0.6f * mContainerWidth, childWidth);
         assertFuzzyEquals("Child height as 60% of the container",
@@ -205,10 +190,10 @@
         if (Build.VERSION.SDK_INT >= 17) {
             // Force our child to inherit parent's layout direction
             onView(withId(R.id.child_margin_start)).perform(
-                    LayoutDirectionActions.setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_INHERIT));
+                    setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_INHERIT));
             // And force the container to RTL mode
             onView(withId(R.id.container)).perform(
-                    LayoutDirectionActions.setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_RTL));
+                    setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_RTL));
 
             // Force a full measure + layout pass on the container
             mPercentFrameLayout.measure(
@@ -249,10 +234,10 @@
         if (Build.VERSION.SDK_INT >= 17) {
             // Force our child to inherit parent's layout direction
             onView(withId(R.id.child_margin_end)).perform(
-                    LayoutDirectionActions.setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_INHERIT));
+                    setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_INHERIT));
             // And force the container to RTL mode
             onView(withId(R.id.container)).perform(
-                    LayoutDirectionActions.setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_RTL));
+                    setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_RTL));
 
             // Force a full measure + layout pass on the container
             mPercentFrameLayout.measure(
diff --git a/percent/tests/java/android/support/percent/PercentRelativeRtlTest.java b/percent/tests/java/android/support/percent/PercentRelativeRtlTest.java
new file mode 100644
index 0000000..634637c
--- /dev/null
+++ b/percent/tests/java/android/support/percent/PercentRelativeRtlTest.java
@@ -0,0 +1,281 @@
+/*
+ * 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.percent;
+
+import android.os.Build;
+import android.support.percent.test.R;
+import android.support.v4.view.ViewCompat;
+import android.test.UiThreadTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.View;
+import org.junit.Before;
+import org.junit.Test;
+
+import static android.support.percent.LayoutDirectionActions.setLayoutDirection;
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+
+/**
+ * The arrangement of child views in the layout class in the default LTR (left-to-right) direction
+ * is as follows:
+ *
+ *  +---------------------------------------------+
+ *  |                                             |
+ *  |    TTTTTTTTTTTTTTTTTTTTT                    |
+ *  |                                             |
+ *  | S                                           |
+ *  | S           CCCCCCCCCCCCCCCCCC              |
+ *  | S           CCCCCCCCCCCCCCCCCC              |
+ *  | S           CCCCCCCCCCCCCCCCCC           E  |
+ *  | S           CCCCCCCCCCCCCCCCCC           E  |
+ *  | S           CCCCCCCCCCCCCCCCCC           E  |
+ *  |             CCCCCCCCCCCCCCCCCC           E  |
+ *  |             CCCCCCCCCCCCCCCCCC           E  |
+ *  |                                          E  |
+ *  |                                             |
+ *  |                    BBBBBBBBBBBBBBBBBBBBB    |
+ *  |                                             |
+ *  +---------------------------------------------+
+ *
+ * The arrangement of child views in the layout class in the RTL (right-to-left) direction
+ * is as follows:
+ *
+ *  +---------------------------------------------+
+ *  |                                             |
+ *  |                    TTTTTTTTTTTTTTTTTTTTT    |
+ *  |                                             |
+ *  |                                          S  |
+ *  |             CCCCCCCCCCCCCCCCCC           S  |
+ *  |             CCCCCCCCCCCCCCCCCC           S  |
+ *  | E           CCCCCCCCCCCCCCCCCC           S  |
+ *  | E           CCCCCCCCCCCCCCCCCC           S  |
+ *  | E           CCCCCCCCCCCCCCCCCC           S  |
+ *  | E           CCCCCCCCCCCCCCCCCC              |
+ *  | E           CCCCCCCCCCCCCCCCCC              |
+ *  | E                                           |
+ *  |                                             |
+ *  |    BBBBBBBBBBBBBBBBBBBBB                    |
+ *  |                                             |
+ *  +---------------------------------------------+
+ *
+ * Child views are exercising the following percent-based constraints supported by
+ * <code>PercentRelativeLayout</code>:
+ *
+ * <ul>
+ *     <li>Top child (marked with T) - width, aspect ratio, top margin, start margin.</li>
+ *     <li>Start child (marked with S) - height, aspect ratio, top margin, start margin.</li>
+ *     <li>Bottom child (marked with B) - width, aspect ratio, bottom margin, end margin.</li>
+ *     <li>Right child (marked with E) - height, aspect ratio, bottom margin, end margin.</li>
+ *     <li>Center child (marked with C) - margin (all sides) from the other four children.</li>
+ * </ul>
+ *
+ * Under LTR direction (pre-v17 devices and v17+ with default direction of en-US locale) we are
+ * testing the same assertions as <code>PercentRelativeTest</code>. Under RTL direction (on v17+
+ * devices with Espresso-powered direction switch) we are testing the reverse assertions along the
+ * X axis for all child views.
+ */
+@SmallTest
+public class PercentRelativeRtlTest extends BaseInstrumentationTestCase<TestRelativeRtlActivity> {
+    private PercentRelativeLayout mPercentRelativeLayout;
+    private int mContainerWidth;
+    private int mContainerHeight;
+
+    public PercentRelativeRtlTest() {
+        super(TestRelativeRtlActivity.class);
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        final TestRelativeRtlActivity activity = mActivityTestRule.getActivity();
+        mPercentRelativeLayout = (PercentRelativeLayout) activity.findViewById(R.id.container);
+        mContainerWidth = mPercentRelativeLayout.getWidth();
+        mContainerHeight = mPercentRelativeLayout.getHeight();
+    }
+
+    private void switchToRtl() {
+        // Force the container to RTL mode
+        onView(withId(R.id.container)).perform(
+                setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_RTL));
+
+        // Force a full measure + layout pass on the container
+        mPercentRelativeLayout.measure(
+                View.MeasureSpec.makeMeasureSpec(mContainerWidth, View.MeasureSpec.EXACTLY),
+                View.MeasureSpec.makeMeasureSpec(mContainerHeight, View.MeasureSpec.EXACTLY));
+        mPercentRelativeLayout.layout(mPercentRelativeLayout.getLeft(),
+                mPercentRelativeLayout.getTop(), mPercentRelativeLayout.getRight(),
+                mPercentRelativeLayout.getBottom());
+    }
+
+    @Test
+    @UiThreadTest
+    public void testTopChild() {
+        final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_top);
+
+        if (Build.VERSION.SDK_INT >= 17) {
+            switchToRtl();
+
+            final int childRight = childToTest.getRight();
+            assertFuzzyEquals("Child start margin as 20% of the container",
+                    0.2f * mContainerWidth, mContainerWidth - childRight);
+        } else {
+            final int childLeft = childToTest.getLeft();
+            assertFuzzyEquals("Child start margin as 20% of the container",
+                    0.2f * mContainerWidth, childLeft);
+        }
+
+        final int childTop = childToTest.getTop();
+        assertFuzzyEquals("Child top margin as 5% of the container",
+                0.05f * mContainerHeight, childTop);
+
+        final int childWidth = childToTest.getWidth();
+        final int childHeight = childToTest.getHeight();
+
+        assertFuzzyEquals("Child width as 50% of the container",
+                0.5f * mContainerWidth, childWidth);
+        assertFuzzyEquals("Child aspect ratio of 2000%",
+                0.05f * childWidth, childHeight);
+    }
+
+    @Test
+    @UiThreadTest
+    public void testStartChild() {
+        final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_start);
+
+        if (Build.VERSION.SDK_INT >= 17) {
+            switchToRtl();
+
+            final int childRight = childToTest.getRight();
+            assertFuzzyEquals("Child start margin as 5% of the container",
+                    0.05f * mContainerWidth, mContainerWidth - childRight);
+        } else {
+            final int childLeft = childToTest.getLeft();
+            assertFuzzyEquals("Child start margin as 5% of the container",
+                    0.05f * mContainerWidth, childLeft);
+        }
+
+        final int childWidth = childToTest.getWidth();
+        final int childHeight = childToTest.getHeight();
+
+        assertFuzzyEquals("Child height as 50% of the container",
+                0.5f * mContainerHeight, childHeight);
+        assertFuzzyEquals("Child aspect ratio of 5%",
+                0.05f * childHeight, childWidth);
+
+        final int childTop = childToTest.getTop();
+
+        assertFuzzyEquals("Child top margin as 20% of the container",
+                0.2f * mContainerHeight, childTop);
+    }
+
+    @Test
+    @UiThreadTest
+    public void testBottomChild() {
+        final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_bottom);
+
+        if (Build.VERSION.SDK_INT >= 17) {
+            switchToRtl();
+
+            final int childLeft = childToTest.getLeft();
+            assertFuzzyEquals("Child end margin as 20% of the container",
+                    0.2f * mContainerWidth, childLeft);
+        } else {
+            final int childRight = childToTest.getRight();
+            assertFuzzyEquals("Child end margin as 20% of the container",
+                    0.2f * mContainerWidth, mContainerWidth - childRight);
+        }
+
+
+        final int childWidth = childToTest.getWidth();
+        final int childHeight = childToTest.getHeight();
+
+        assertFuzzyEquals("Child width as 40% of the container",
+                0.4f * mContainerWidth, childWidth);
+        assertFuzzyEquals("Child aspect ratio of 2000%",
+                0.05f * childWidth, childHeight);
+
+        final int childBottom = childToTest.getBottom();
+
+        assertFuzzyEquals("Child bottom margin as 5% of the container",
+                0.05f * mContainerHeight, mContainerHeight - childBottom);
+    }
+
+    @Test
+    @UiThreadTest
+    public void testEndChild() {
+        final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_end);
+
+        if (Build.VERSION.SDK_INT >= 17) {
+            switchToRtl();
+
+            final int childLeft = childToTest.getLeft();
+            assertFuzzyEquals("Child end margin as 5% of the container",
+                    0.05f * mContainerWidth, childLeft);
+        } else {
+            final int childRight = childToTest.getRight();
+            assertFuzzyEquals("Child end margin as 5% of the container",
+                    0.05f * mContainerWidth, mContainerWidth - childRight);
+        }
+
+        final int childWidth = childToTest.getWidth();
+        final int childHeight = childToTest.getHeight();
+
+        assertFuzzyEquals("Child height as 50% of the container",
+                0.4f * mContainerHeight, childHeight);
+        assertFuzzyEquals("Child aspect ratio of 5%",
+                0.05f * childHeight, childWidth);
+
+        final int childBottom = childToTest.getBottom();
+
+        assertFuzzyEquals("Child bottom margin as 20% of the container",
+                0.2f * mContainerHeight, mContainerHeight - childBottom);
+    }
+
+    @Test
+    @UiThreadTest
+    public void testCenterChild() {
+        final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_center);
+
+        boolean supportsRtl = Build.VERSION.SDK_INT >= 17;
+        if (supportsRtl) {
+            switchToRtl();
+        }
+
+        final int childLeft = childToTest.getLeft();
+        final int childTop = childToTest.getTop();
+        final int childRight = childToTest.getRight();
+        final int childBottom = childToTest.getBottom();
+
+        final View leftChild = supportsRtl
+                ? mPercentRelativeLayout.findViewById(R.id.child_end)
+                : mPercentRelativeLayout.findViewById(R.id.child_start);
+        assertFuzzyEquals("Child left margin as 10% of the container",
+                leftChild.getRight() + 0.1f * mContainerWidth, childLeft);
+
+        final View topChild = mPercentRelativeLayout.findViewById(R.id.child_top);
+        assertFuzzyEquals("Child top margin as 10% of the container",
+                topChild.getBottom() + 0.1f * mContainerHeight, childTop);
+
+        final View rightChild = supportsRtl
+                ? mPercentRelativeLayout.findViewById(R.id.child_start)
+                : mPercentRelativeLayout.findViewById(R.id.child_end);
+        assertFuzzyEquals("Child right margin as 10% of the container",
+                rightChild.getLeft() - 0.1f * mContainerWidth, childRight);
+
+        final View bottomChild = mPercentRelativeLayout.findViewById(R.id.child_bottom);
+        assertFuzzyEquals("Child bottom margin as 10% of the container",
+                bottomChild.getTop() - 0.1f * mContainerHeight, childBottom);
+    }
+}
diff --git a/percent/tests/java/android/support/percent/PercentRelativeTest.java b/percent/tests/java/android/support/percent/PercentRelativeTest.java
new file mode 100644
index 0000000..31d406e
--- /dev/null
+++ b/percent/tests/java/android/support/percent/PercentRelativeTest.java
@@ -0,0 +1,189 @@
+/*
+ * 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.percent;
+
+import android.support.percent.test.R;
+import android.test.UiThreadTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.View;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * The arrangement of child views in the layout class is as follows:
+ *
+ *  +---------------------------------------------+
+ *  |                                             |
+ *  |    TTTTTTTTTTTTTTTTTTTTT                    |
+ *  |                                             |
+ *  | L                                           |
+ *  | L           CCCCCCCCCCCCCCCCCC              |
+ *  | L           CCCCCCCCCCCCCCCCCC              |
+ *  | L           CCCCCCCCCCCCCCCCCC           R  |
+ *  | L           CCCCCCCCCCCCCCCCCC           R  |
+ *  | L           CCCCCCCCCCCCCCCCCC           R  |
+ *  |             CCCCCCCCCCCCCCCCCC           R  |
+ *  |             CCCCCCCCCCCCCCCCCC           R  |
+ *  |                                          R  |
+ *  |                                             |
+ *  |                    BBBBBBBBBBBBBBBBBBBBB    |
+ *  |                                             |
+ *  +---------------------------------------------+
+ *
+ * Child views are exercising the following percent-based constraints supported by
+ * <code>PercentRelativeLayout</code>:
+ *
+ * <ul>
+ *     <li>Top child (marked with T) - width, aspect ratio, top margin, left margin.</li>
+ *     <li>Left child (marked with L) - height, aspect ratio, top margin, left margin.</li>
+ *     <li>Bottom child (marked with B) - width, aspect ratio, bottom margin, right margin.</li>
+ *     <li>Right child (marked with R) - height, aspect ratio, bottom margin, right margin.</li>
+ *     <li>Center child (marked with C) - margin (all sides) from the other four children.</li>
+ * </ul>
+ */
+@SmallTest
+public class PercentRelativeTest extends BaseInstrumentationTestCase<TestRelativeActivity> {
+    private PercentRelativeLayout mPercentRelativeLayout;
+    private int mContainerWidth;
+    private int mContainerHeight;
+
+    public PercentRelativeTest() {
+        super(TestRelativeActivity.class);
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        final TestRelativeActivity activity = mActivityTestRule.getActivity();
+        mPercentRelativeLayout = (PercentRelativeLayout) activity.findViewById(R.id.container);
+        mContainerWidth = mPercentRelativeLayout.getWidth();
+        mContainerHeight = mPercentRelativeLayout.getHeight();
+    }
+
+    @Test
+    @UiThreadTest
+    public void testTopChild() {
+        final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_top);
+
+        final int childWidth = childToTest.getWidth();
+        final int childHeight = childToTest.getHeight();
+
+        assertFuzzyEquals("Child width as 50% of the container",
+                0.5f * mContainerWidth, childWidth);
+        assertFuzzyEquals("Child aspect ratio of 2000%",
+                0.05f * childWidth, childHeight);
+
+        final int childLeft = childToTest.getLeft();
+        final int childTop = childToTest.getTop();
+
+        assertFuzzyEquals("Child left margin as 20% of the container",
+                0.2f * mContainerWidth, childLeft);
+        assertFuzzyEquals("Child top margin as 5% of the container",
+                0.05f * mContainerHeight, childTop);
+    }
+
+    @Test
+    @UiThreadTest
+    public void testLeftChild() {
+        final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_left);
+
+        final int childWidth = childToTest.getWidth();
+        final int childHeight = childToTest.getHeight();
+
+        assertFuzzyEquals("Child height as 50% of the container",
+                0.5f * mContainerHeight, childHeight);
+        assertFuzzyEquals("Child aspect ratio of 5%",
+                0.05f * childHeight, childWidth);
+
+        final int childLeft = childToTest.getLeft();
+        final int childTop = childToTest.getTop();
+
+        assertFuzzyEquals("Child left margin as 5% of the container",
+                0.05f * mContainerWidth, childLeft);
+        assertFuzzyEquals("Child top margin as 20% of the container",
+                0.2f * mContainerHeight, childTop);
+    }
+
+    @Test
+    @UiThreadTest
+    public void testBottomChild() {
+        final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_bottom);
+
+        final int childWidth = childToTest.getWidth();
+        final int childHeight = childToTest.getHeight();
+
+        assertFuzzyEquals("Child width as 40% of the container",
+                0.4f * mContainerWidth, childWidth);
+        assertFuzzyEquals("Child aspect ratio of 2000%",
+                0.05f * childWidth, childHeight);
+
+        final int childRight = childToTest.getRight();
+        final int childBottom = childToTest.getBottom();
+
+        assertFuzzyEquals("Child right margin as 20% of the container",
+                0.2f * mContainerWidth, mContainerWidth - childRight);
+        assertFuzzyEquals("Child bottom margin as 5% of the container",
+                0.05f * mContainerHeight, mContainerHeight - childBottom);
+    }
+
+    @Test
+    @UiThreadTest
+    public void testRightChild() {
+        final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_right);
+
+        final int childWidth = childToTest.getWidth();
+        final int childHeight = childToTest.getHeight();
+
+        assertFuzzyEquals("Child height as 50% of the container",
+                0.4f * mContainerHeight, childHeight);
+        assertFuzzyEquals("Child aspect ratio of 5%",
+                0.05f * childHeight, childWidth);
+
+        final int childRight = childToTest.getRight();
+        final int childBottom = childToTest.getBottom();
+
+        assertFuzzyEquals("Child right margin as 5% of the container",
+                0.05f * mContainerWidth, mContainerWidth - childRight);
+        assertFuzzyEquals("Child bottom margin as 20% of the container",
+                0.2f * mContainerHeight, mContainerHeight - childBottom);
+    }
+
+    @Test
+    @UiThreadTest
+    public void testCenterChild() {
+        final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_center);
+
+        final int childLeft = childToTest.getLeft();
+        final int childTop = childToTest.getTop();
+        final int childRight = childToTest.getRight();
+        final int childBottom = childToTest.getBottom();
+
+        final View leftChild = mPercentRelativeLayout.findViewById(R.id.child_left);
+        assertFuzzyEquals("Child left margin as 10% of the container",
+                leftChild.getRight() + 0.1f * mContainerWidth, childLeft);
+
+        final View topChild = mPercentRelativeLayout.findViewById(R.id.child_top);
+        assertFuzzyEquals("Child top margin as 10% of the container",
+                topChild.getBottom() + 0.1f * mContainerHeight, childTop);
+
+        final View rightChild = mPercentRelativeLayout.findViewById(R.id.child_right);
+        assertFuzzyEquals("Child right margin as 10% of the container",
+                rightChild.getLeft() - 0.1f * mContainerWidth, childRight);
+
+        final View bottomChild = mPercentRelativeLayout.findViewById(R.id.child_bottom);
+        assertFuzzyEquals("Child bottom margin as 10% of the container",
+                bottomChild.getTop() - 0.1f * mContainerHeight, childBottom);
+    }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/ViewInfoStoreTest.java b/percent/tests/java/android/support/percent/TestRelativeActivity.java
similarity index 70%
rename from v7/recyclerview/tests/src/android/support/v7/widget/ViewInfoStoreTest.java
rename to percent/tests/java/android/support/percent/TestRelativeActivity.java
index 4c78e3d..e0d451f 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/ViewInfoStoreTest.java
+++ b/percent/tests/java/android/support/percent/TestRelativeActivity.java
@@ -13,8 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.support.v7.widget;
 
-public class ViewInfoStoreTest {
+package android.support.percent;
 
+import android.support.percent.test.R;
+
+public class TestRelativeActivity extends BaseTestActivity {
+    @Override
+    protected int getContentViewLayoutResId() {
+        return R.layout.percent_relative_layout;
+    }
 }
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/ViewInfoStoreTest.java b/percent/tests/java/android/support/percent/TestRelativeRtlActivity.java
similarity index 64%
copy from v7/recyclerview/tests/src/android/support/v7/widget/ViewInfoStoreTest.java
copy to percent/tests/java/android/support/percent/TestRelativeRtlActivity.java
index 4c78e3d..02a2fff 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/ViewInfoStoreTest.java
+++ b/percent/tests/java/android/support/percent/TestRelativeRtlActivity.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.
@@ -13,8 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.support.v7.widget;
 
-public class ViewInfoStoreTest {
+package android.support.percent;
 
+import android.support.percent.test.R;
+
+public class TestRelativeRtlActivity extends BaseTestActivity {
+    @Override
+    protected int getContentViewLayoutResId() {
+        return R.layout.percent_relative_layout_rtl;
+    }
 }
diff --git a/percent/tests/res/layout/percent_relative_layout.xml b/percent/tests/res/layout/percent_relative_layout.xml
new file mode 100644
index 0000000..7bdfe9b
--- /dev/null
+++ b/percent/tests/res/layout/percent_relative_layout.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.
+-->
+
+<android.support.percent.PercentRelativeLayout
+    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">
+
+    <!-- This child is anchored along the top edge of the parent, testing percent-based
+         left margin, top margin and width, as well as the aspect ratio. -->
+    <View
+        android:id="@+id/child_top"
+        android:layout_alignParentTop="true"
+        app:layout_widthPercent="50%"
+        app:layout_aspectRatio="2000%"
+        app:layout_marginLeftPercent="20%"
+        app:layout_marginTopPercent="5%"
+        android:background="#5690E0" />
+
+    <!-- This child is anchored along the left edge of the parent, testing percent-based
+         left margin, top margin and height, as well as the aspect ratio. -->
+    <View
+        android:id="@+id/child_left"
+        android:layout_alignParentLeft="true"
+        app:layout_heightPercent="50%"
+        app:layout_aspectRatio="5%"
+        app:layout_marginLeftPercent="5%"
+        app:layout_marginTopPercent="20%"
+        android:background="#902030"  />
+
+    <!-- This child is anchored along the bottom edge of the parent, testing percent-based
+         right margin, bottom margin and width, as well as the aspect ratio. -->
+    <View
+        android:id="@+id/child_bottom"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentRight="true"
+        app:layout_widthPercent="40%"
+        app:layout_aspectRatio="2000%"
+        app:layout_marginRightPercent="20%"
+        app:layout_marginBottomPercent="5%"
+        android:background="#A0B040"  />
+
+    <!-- This child is anchored along the right edge of the parent, testing percent-based
+         right margin, bottom margin and height, as well as the aspect ratio. -->
+    <View
+        android:id="@+id/child_right"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentRight="true"
+        app:layout_heightPercent="40%"
+        app:layout_aspectRatio="5%"
+        app:layout_marginRightPercent="5%"
+        app:layout_marginBottomPercent="20%"
+        android:background="#20C0A0" />
+
+    <!-- This child is anchored to be in the "center" of the parent by anchoring it with
+         percent-based margins relatively to its edge-aligned siblings. -->
+    <View
+        android:id="@+id/child_center"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_below="@+id/child_top"
+        android:layout_toRightOf="@+id/child_left"
+        android:layout_toLeftOf="@+id/child_right"
+        android:layout_above="@+id/child_bottom"
+        app:layout_marginPercent="10%"
+        android:background="#F07020"  />
+
+</android.support.percent.PercentRelativeLayout>
+
diff --git a/percent/tests/res/layout/percent_relative_layout_rtl.xml b/percent/tests/res/layout/percent_relative_layout_rtl.xml
new file mode 100644
index 0000000..2d0e813
--- /dev/null
+++ b/percent/tests/res/layout/percent_relative_layout_rtl.xml
@@ -0,0 +1,97 @@
+<?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.
+-->
+
+<!-- Test layout for testing RTL-aware attributes on PercentRelativeLayout. Note that
+     we do not need to use layout_marginStartPercent *and* layout_marginLeftPercent
+     on the same child view to make it work correctly on different platform versions,
+     while we do have to use layout_alignParentEnd *and* layout_alignParentRight (as
+     the core RelativeLayout needs the old attribute on older pre-v17 platform versions).
+-->
+<android.support.percent.PercentRelativeLayout
+    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">
+
+    <!-- This child is anchored along the top edge of the parent, testing percent-based
+         start margin, top margin and width, as well as the aspect ratio. -->
+    <View
+        android:id="@+id/child_top"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentStart="true"
+        android:layout_alignParentLeft="true"
+        app:layout_widthPercent="50%"
+        app:layout_aspectRatio="2000%"
+        app:layout_marginStartPercent="20%"
+        app:layout_marginTopPercent="5%"
+        android:background="#5690E0" />
+
+    <!-- This child is anchored along the start edge of the parent, testing percent-based
+         start margin, top margin and height, as well as the aspect ratio. -->
+    <View
+        android:id="@+id/child_start"
+        android:layout_alignParentStart="true"
+        android:layout_alignParentLeft="true"
+        app:layout_heightPercent="50%"
+        app:layout_aspectRatio="5%"
+        app:layout_marginStartPercent="5%"
+        app:layout_marginTopPercent="20%"
+        android:background="#902030" />
+
+    <!-- This child is anchored along the bottom edge of the parent, testing percent-based
+         end margin, bottom margin and width, as well as the aspect ratio. -->
+    <View
+        android:id="@+id/child_bottom"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentRight="true"
+        android:layout_alignParentEnd="true"
+        app:layout_widthPercent="40%"
+        app:layout_aspectRatio="2000%"
+        app:layout_marginEndPercent="20%"
+        app:layout_marginBottomPercent="5%"
+        android:background="#A0B040" />
+
+    <!-- This child is anchored along the end edge of the parent, testing percent-based
+         end margin, bottom margin and height, as well as the aspect ratio. -->
+    <View
+        android:id="@+id/child_end"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentRight="true"
+        android:layout_alignParentEnd="true"
+        app:layout_heightPercent="40%"
+        app:layout_aspectRatio="5%"
+        app:layout_marginEndPercent="5%"
+        app:layout_marginBottomPercent="20%"
+        android:background="#20C0A0" />
+
+    <!-- This child is anchored to be in the "center" of the parent by anchoring it with
+         percent-based margins relatively to its edge-aligned siblings. -->
+    <View
+        android:id="@+id/child_center"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_below="@+id/child_top"
+        android:layout_toEndOf="@+id/child_start"
+        android:layout_toRightOf="@+id/child_start"
+        android:layout_toStartOf="@+id/child_end"
+        android:layout_toLeftOf="@+id/child_end"
+        android:layout_above="@+id/child_bottom"
+        app:layout_marginPercent="10%"
+        android:background="#F07020" />
+
+</android.support.percent.PercentRelativeLayout>
+
diff --git a/recommendation/build.gradle b/recommendation/build.gradle
index a9db906..c38fbd2 100644
--- a/recommendation/build.gradle
+++ b/recommendation/build.gradle
@@ -1,4 +1,4 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
 
 archivesBaseName = 'recommendation'
 
@@ -7,7 +7,7 @@
 }
 
 android {
-    compileSdkVersion 'current'
+    compileSdkVersion project.ext.currentSdk
 
     defaultConfig {
         minSdkVersion 21
diff --git a/v13/build.gradle b/v13/build.gradle
index c1e624b..5060842 100644
--- a/v13/build.gradle
+++ b/v13/build.gradle
@@ -1,4 +1,4 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
 
 archivesBaseName = 'support-v13'
 
@@ -74,6 +74,11 @@
         // TODO: fix errors and reenable.
         abortOnError false
     }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_7
+        targetCompatibility JavaVersion.VERSION_1_7
+    }
 }
 
 android.libraryVariants.all { variant ->
diff --git a/v14/preference/build.gradle b/v14/preference/build.gradle
index b5129b1..79b96cd 100644
--- a/v14/preference/build.gradle
+++ b/v14/preference/build.gradle
@@ -16,7 +16,7 @@
 
 
 
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
 
 archivesBaseName = 'preference-v14'
 
@@ -28,7 +28,7 @@
 }
 
 android {
-    compileSdkVersion 'current'
+    compileSdkVersion project.ext.currentSdk
 
     sourceSets {
         main.manifest.srcFile 'AndroidManifest.xml'
diff --git a/v17/leanback/api/current.txt b/v17/leanback/api/current.txt
index 0df488f..d90f9f6 100644
--- a/v17/leanback/api/current.txt
+++ b/v17/leanback/api/current.txt
@@ -1030,9 +1030,12 @@
     method public android.content.Intent getIntent();
     method public java.util.List<android.support.v17.leanback.widget.GuidedAction> getSubActions();
     method public java.lang.CharSequence getTitle();
+    method public boolean hasAnyEditable();
+    method public boolean hasEditableActivatorView();
     method public boolean hasMultilineDescription();
     method public boolean hasNext();
     method public boolean hasSubActions();
+    method public boolean hasTextEditable();
     method public boolean infoOnly();
     method public boolean isChecked();
     method public boolean isDescriptionEditable();
@@ -1071,36 +1074,36 @@
   public static abstract class GuidedAction.BuilderBase {
     ctor public GuidedAction.BuilderBase(android.content.Context);
     method protected final void applyValues(android.support.v17.leanback.widget.GuidedAction);
-    method public abstract T build();
-    method public android.support.v17.leanback.widget.GuidedAction.BuilderBase<T> checkSetId(int);
-    method public android.support.v17.leanback.widget.GuidedAction.BuilderBase<T> checked(boolean);
-    method public android.support.v17.leanback.widget.GuidedAction.BuilderBase<T> clickAction(long);
-    method public android.support.v17.leanback.widget.GuidedAction.BuilderBase<T> description(java.lang.CharSequence);
-    method public android.support.v17.leanback.widget.GuidedAction.BuilderBase<T> description(int);
-    method public android.support.v17.leanback.widget.GuidedAction.BuilderBase<T> descriptionEditInputType(int);
-    method public android.support.v17.leanback.widget.GuidedAction.BuilderBase<T> descriptionEditable(boolean);
-    method public android.support.v17.leanback.widget.GuidedAction.BuilderBase<T> descriptionInputType(int);
-    method public android.support.v17.leanback.widget.GuidedAction.BuilderBase<T> editDescription(java.lang.CharSequence);
-    method public android.support.v17.leanback.widget.GuidedAction.BuilderBase<T> editDescription(int);
-    method public android.support.v17.leanback.widget.GuidedAction.BuilderBase<T> editInputType(int);
-    method public android.support.v17.leanback.widget.GuidedAction.BuilderBase<T> editTitle(java.lang.CharSequence);
-    method public android.support.v17.leanback.widget.GuidedAction.BuilderBase<T> editTitle(int);
-    method public android.support.v17.leanback.widget.GuidedAction.BuilderBase<T> editable(boolean);
-    method public android.support.v17.leanback.widget.GuidedAction.BuilderBase<T> enabled(boolean);
-    method public android.support.v17.leanback.widget.GuidedAction.BuilderBase<T> focusable(boolean);
+    method public B checkSetId(int);
+    method public B checked(boolean);
+    method public B clickAction(long);
+    method public B description(java.lang.CharSequence);
+    method public B description(int);
+    method public B descriptionEditInputType(int);
+    method public B descriptionEditable(boolean);
+    method public B descriptionInputType(int);
+    method public B editDescription(java.lang.CharSequence);
+    method public B editDescription(int);
+    method public B editInputType(int);
+    method public B editTitle(java.lang.CharSequence);
+    method public B editTitle(int);
+    method public B editable(boolean);
+    method public B enabled(boolean);
+    method public B focusable(boolean);
     method public android.content.Context getContext();
-    method public android.support.v17.leanback.widget.GuidedAction.BuilderBase<T> hasNext(boolean);
-    method public android.support.v17.leanback.widget.GuidedAction.BuilderBase<T> icon(android.graphics.drawable.Drawable);
-    method public android.support.v17.leanback.widget.GuidedAction.BuilderBase<T> icon(int);
-    method public deprecated android.support.v17.leanback.widget.GuidedAction.BuilderBase<T> iconResourceId(int, android.content.Context);
-    method public android.support.v17.leanback.widget.GuidedAction.BuilderBase<T> id(long);
-    method public android.support.v17.leanback.widget.GuidedAction.BuilderBase<T> infoOnly(boolean);
-    method public android.support.v17.leanback.widget.GuidedAction.BuilderBase<T> inputType(int);
-    method public android.support.v17.leanback.widget.GuidedAction.BuilderBase<T> intent(android.content.Intent);
-    method public android.support.v17.leanback.widget.GuidedAction.BuilderBase<T> multilineDescription(boolean);
-    method public android.support.v17.leanback.widget.GuidedAction.BuilderBase<T> subActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>);
-    method public android.support.v17.leanback.widget.GuidedAction.BuilderBase<T> title(java.lang.CharSequence);
-    method public android.support.v17.leanback.widget.GuidedAction.BuilderBase<T> title(int);
+    method public B hasEditableActivatorView(boolean);
+    method public B hasNext(boolean);
+    method public B icon(android.graphics.drawable.Drawable);
+    method public B icon(int);
+    method public deprecated B iconResourceId(int, android.content.Context);
+    method public B id(long);
+    method public B infoOnly(boolean);
+    method public B inputType(int);
+    method public B intent(android.content.Intent);
+    method public B multilineDescription(boolean);
+    method public B subActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>);
+    method public B title(java.lang.CharSequence);
+    method public B title(int);
   }
 
   public class GuidedActionEditText extends android.widget.EditText implements android.support.v17.leanback.widget.ImeKeyMonitor {
@@ -1124,6 +1127,7 @@
     method public void onAnimateItemFocused(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, boolean);
     method public void onAnimateItemPressed(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, boolean);
     method public void onAnimateItemPressedCancelled(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder);
+    method public void onBindActivatorView(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, android.support.v17.leanback.widget.GuidedAction);
     method public void onBindCheckMarkView(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, android.support.v17.leanback.widget.GuidedAction);
     method public void onBindChevronView(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, android.support.v17.leanback.widget.GuidedAction);
     method public void onBindViewHolder(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, android.support.v17.leanback.widget.GuidedAction);
@@ -1137,12 +1141,14 @@
     method public int onProvideItemLayoutId();
     method public int onProvideItemLayoutId(int);
     method public int onProvideLayoutId();
+    method public boolean onUpdateActivatorView(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, android.support.v17.leanback.widget.GuidedAction);
     method public void onUpdateExpandedViewHolder(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder);
     method public void setAsButtonActions();
     method public void setEditingMode(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, android.support.v17.leanback.widget.GuidedAction, boolean);
     method public void setExpandedViewHolder(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder);
     method protected void setupImeOptions(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, android.support.v17.leanback.widget.GuidedAction);
     method public void startExpandedTransition(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder);
+    field public static final int VIEW_TYPE_DATE_PICKER = 1; // 0x1
     field public static final int VIEW_TYPE_DEFAULT = 0; // 0x0
   }
 
@@ -1160,10 +1166,32 @@
     method public android.widget.ImageView getIconView();
     method public android.widget.TextView getTitleView();
     method public boolean isInEditing();
+    method public boolean isInEditingActivatorView();
     method public boolean isInEditingDescription();
+    method public boolean isInEditingText();
+    method public boolean isInEditingTitle();
     method public boolean isSubAction();
   }
 
+  public class GuidedDatePickerAction extends android.support.v17.leanback.widget.GuidedAction {
+    ctor public GuidedDatePickerAction();
+    method public long getDate();
+    method public java.lang.String getDatePickerFormat();
+    method public void setDate(long);
+  }
+
+  public static final class GuidedDatePickerAction.Builder extends android.support.v17.leanback.widget.GuidedDatePickerAction.BuilderBase {
+    ctor public GuidedDatePickerAction.Builder(android.content.Context);
+    method public android.support.v17.leanback.widget.GuidedDatePickerAction build();
+  }
+
+  public static abstract class GuidedDatePickerAction.BuilderBase extends android.support.v17.leanback.widget.GuidedAction.BuilderBase {
+    ctor public GuidedDatePickerAction.BuilderBase(android.content.Context);
+    method protected final void applyDatePickerValues(android.support.v17.leanback.widget.GuidedDatePickerAction);
+    method public B date(long);
+    method public B datePickerFormat(java.lang.String);
+  }
+
   public class HeaderItem {
     ctor public HeaderItem(long, java.lang.String);
     ctor public HeaderItem(java.lang.String);
@@ -1945,67 +1973,47 @@
 
 package android.support.v17.leanback.widget.picker {
 
-  public class DatePicker extends android.support.v17.leanback.widget.picker.Picker {
-    ctor public DatePicker(android.content.Context, android.util.AttributeSet, int);
-    method public java.lang.String getDatePickerFormat();
-    method public java.util.Calendar getMaxDate();
-    method public java.util.Calendar getMinDate();
-    method public void setDatePickerFormat(java.lang.String);
-    method public void setMaxDate(long);
-    method public void setMinDate(long);
-    method public void updateDate(int, int, int, boolean);
-  }
-
   public class Picker extends android.widget.FrameLayout {
     ctor public Picker(android.content.Context, android.util.AttributeSet, int);
-    method public void addPickerValueListener(android.support.v17.leanback.widget.picker.Picker.PickerValueListener);
-    method public int getActiveColumn();
+    method public void addOnValueChangedListener(android.support.v17.leanback.widget.picker.Picker.PickerValueListener);
+    method public float getActivatedVisibleItemCount();
     method public android.support.v17.leanback.widget.picker.PickerColumn getColumnAt(int);
     method public int getColumnsCount();
-    method protected int getPickerId();
     method protected int getPickerItemHeightPixels();
-    method protected int getPickerItemLayoutId();
-    method protected int getPickerItemTextViewId();
-    method protected int getPickerSeparatorLayoutId();
-    method protected int getRootLayoutId();
-    method protected java.lang.String getSeparator();
-    method public float getVisiblePickerItems();
-    method public float getVisiblePickerItemsInExpand();
-    method public boolean isExpanded();
-    method public boolean isToggleExpandOnClick();
-    method public void onColumnValueChange(int, int);
-    method public void removePickerValueListener(android.support.v17.leanback.widget.picker.Picker.PickerValueListener);
-    method public void setActiveColumn(int);
-    method public void setColumns(java.util.ArrayList<android.support.v17.leanback.widget.picker.PickerColumn>);
-    method public void setExpanded(boolean);
-    method public void setToggleExpandOnClick(boolean);
-    method public void setVisiblePickerItems(float);
-    method public void setVisiblePickerItemsInExpand(float);
-    method public void updateAdapter(int);
-    method public void updateValue(int, int, boolean);
+    method public final int getPickerItemLayoutId();
+    method public final int getPickerItemTextViewId();
+    method public int getSelectedColumn();
+    method public final java.lang.CharSequence getSeparator();
+    method public float getVisibleItemCount();
+    method public void onColumnValueChanged(int, int);
+    method public void removeOnValueChangedListener(android.support.v17.leanback.widget.picker.Picker.PickerValueListener);
+    method public void setActivatedVisibleItemCount(float);
+    method public void setColumnAt(int, android.support.v17.leanback.widget.picker.PickerColumn);
+    method public void setColumnValue(int, int, boolean);
+    method public void setColumns(java.util.List<android.support.v17.leanback.widget.picker.PickerColumn>);
+    method public final void setPickerItemTextViewId(int);
+    method public void setSelectedColumn(int);
+    method public final void setSeparator(java.lang.CharSequence);
+    method public void setVisibleItemCount(float);
   }
 
   public static abstract interface Picker.PickerValueListener {
     method public abstract void onValueChanged(android.support.v17.leanback.widget.picker.Picker, int);
   }
 
-  public class PickerColumn implements android.os.Parcelable {
+  public class PickerColumn {
     ctor public PickerColumn();
-    ctor public PickerColumn(android.os.Parcel);
-    method public int describeContents();
     method public int getCurrentValue();
-    method public int getItemsCount();
+    method public java.lang.CharSequence getEntryAt(int);
+    method public java.lang.String getEntryFormat();
+    method public int getItemCount();
     method public int getMaxValue();
     method public int getMinValue();
-    method public java.lang.String getValueLabelAt(int);
-    method public java.lang.String getValueLabelFormat();
-    method public boolean setCurrentValue(int);
-    method public boolean setMaxValue(int);
-    method public boolean setMinValue(int);
-    method public void setValueLabelFormat(java.lang.String);
-    method public void setValueStaticLabels(java.lang.String[]);
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static android.os.Parcelable.Creator<android.support.v17.leanback.widget.picker.PickerColumn> CREATOR;
+    method public void setCurrentValue(int);
+    method public void setEntries(java.lang.CharSequence[]);
+    method public void setEntryFormat(java.lang.String);
+    method public void setMaxValue(int);
+    method public void setMinValue(int);
   }
 
 }
diff --git a/v17/leanback/build.gradle b/v17/leanback/build.gradle
index 401a5c4..eed7d6b 100644
--- a/v17/leanback/build.gradle
+++ b/v17/leanback/build.gradle
@@ -1,4 +1,4 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
 
 archivesBaseName = 'leanback-v17'
 
@@ -9,7 +9,7 @@
 
 android {
     // WARNING: should be 17
-    compileSdkVersion 'current'
+    compileSdkVersion project.ext.currentSdk
 
     defaultConfig {
         minSdkVersion 17
diff --git a/v17/leanback/res/layout/lb_guidance.xml b/v17/leanback/res/layout/lb_guidance.xml
index 28c0220..59f8ac5 100644
--- a/v17/leanback/res/layout/lb_guidance.xml
+++ b/v17/leanback/res/layout/lb_guidance.xml
@@ -20,6 +20,7 @@
     android:layout_height="match_parent" >
 
     <RelativeLayout
+        android:id="@+id/guidance_container"
         style="?attr/guidanceContainerStyle" >
 
         <ImageView
diff --git a/v17/leanback/res/layout/lb_guidedactions_datepicker_item.xml b/v17/leanback/res/layout/lb_guidedactions_datepicker_item.xml
new file mode 100644
index 0000000..98278d9
--- /dev/null
+++ b/v17/leanback/res/layout/lb_guidedactions_datepicker_item.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<!-- Layout for an action item displayed in the 2 pane actions fragment. -->
+<android.support.v17.leanback.widget.NonOverlappingLinearLayoutWithForeground
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    style="?attr/guidedActionItemContainerStyle" >
+
+    <ImageView
+        android:id="@+id/guidedactions_item_icon"
+        style="?attr/guidedActionItemIconStyle"
+        tools:ignore="ContentDescription" />
+
+    <TextView
+        android:id="@+id/guidedactions_item_title"
+        android:layout_width="wrap_content"
+        android:layout_gravity="center_vertical"
+        style="?attr/guidedActionItemTitleStyle" />
+
+    <android.support.v17.leanback.widget.picker.DatePicker
+        android:id="@+id/guidedactions_activator_item"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+</android.support.v17.leanback.widget.NonOverlappingLinearLayoutWithForeground>
diff --git a/v17/leanback/res/layout/lb_guidedstep_fragment.xml b/v17/leanback/res/layout/lb_guidedstep_fragment.xml
index d4983e5..2ffae70 100644
--- a/v17/leanback/res/layout/lb_guidedstep_fragment.xml
+++ b/v17/leanback/res/layout/lb_guidedstep_fragment.xml
@@ -18,55 +18,66 @@
 <android.support.v17.leanback.app.GuidedStepRootLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/guidedstep_root"
-    android:orientation="horizontal"
     android:layout_width="match_parent"
-    android:layout_height="match_parent" >
-    <LinearLayout
-        android:id="@+id/content_frame"
-        android:orientation="horizontal"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:gravity="bottom"
+    android:weightSum="2">
+
+    <FrameLayout
+        android:id="@+id/guidedstep_background_view_root"
         android:layout_width="match_parent"
-        android:layout_height="match_parent" >
+        android:layout_height="0dp"
+        android:layout_weight="?attr/guidedStepHeightWeight">
 
-        <android.support.v17.leanback.widget.NonOverlappingFrameLayout
-            android:id="@+id/content_fragment"
-            android:layout_toStartOf="@+id/action_fragment"
-            android:layout_width="0dp"
-            android:layout_weight="1"
-            android:layout_height="match_parent"
-            android:layout_alignParentStart="true" />
-
-        <android.support.v17.leanback.widget.NonOverlappingFrameLayout
-            android:id="@+id/action_fragment_root"
-            android:transitionName="action_fragment_root"
-            android:transitionGroup="false"
+        <LinearLayout
+            android:id="@+id/content_frame"
             android:orientation="horizontal"
-            android:clipToPadding="false"
-            android:clipChildren="false"
-            android:paddingStart="@dimen/lb_guidedactions_section_shadow_width"
-            android:layout_width="0dp"
-            android:layout_weight="?attr/guidedActionContentWidthWeight"
-            android:layout_height="match_parent"
-            android:layout_alignParentEnd="true">
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
 
-            <android.support.v17.leanback.widget.NonOverlappingView
-                android:id="@+id/action_fragment_background"
-                android:transitionName="action_fragment_background"
-                android:orientation="horizontal"
-                android:outlineProvider="paddedBounds"
-                android:layout_width="match_parent"
+            <android.support.v17.leanback.widget.NonOverlappingFrameLayout
+                android:id="@+id/content_fragment"
+                android:layout_width="0dp"
+                android:layout_weight="1"
                 android:layout_height="match_parent"
-                android:background="?attr/guidedActionsBackground"
-                android:elevation="?attr/guidedActionsElevation" />
+                android:layout_alignParentStart="true" />
 
-            <android.support.v17.leanback.widget.NonOverlappingLinearLayout
-                android:id="@+id/action_fragment"
-                android:transitionName="action_fragment"
+            <android.support.v17.leanback.widget.NonOverlappingFrameLayout
+                android:id="@+id/action_fragment_root"
+                android:transitionName="action_fragment_root"
                 android:transitionGroup="false"
                 android:orientation="horizontal"
-                android:layout_width="match_parent"
+                android:clipToPadding="false"
+                android:clipChildren="false"
+                android:paddingStart="@dimen/lb_guidedactions_section_shadow_width"
+                android:layout_width="0dp"
+                android:layout_weight="?attr/guidedActionContentWidthWeight"
                 android:layout_height="match_parent"
-                android:elevation="?attr/guidedActionsElevation" />
-        </android.support.v17.leanback.widget.NonOverlappingFrameLayout>
+                android:layout_alignParentEnd="true">
 
-    </LinearLayout>
-</android.support.v17.leanback.app.GuidedStepRootLayout>
\ No newline at end of file
+                <android.support.v17.leanback.widget.NonOverlappingView
+                    android:id="@+id/action_fragment_background"
+                    android:transitionName="action_fragment_background"
+                    android:orientation="horizontal"
+                    android:outlineProvider="paddedBounds"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:background="?attr/guidedActionsBackground"
+                    android:elevation="?attr/guidedActionsElevation" />
+
+                <android.support.v17.leanback.widget.NonOverlappingLinearLayout
+                    android:id="@+id/action_fragment"
+                    android:transitionName="action_fragment"
+                    android:transitionGroup="false"
+                    android:orientation="horizontal"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:elevation="?attr/guidedActionsElevation" />
+            </android.support.v17.leanback.widget.NonOverlappingFrameLayout>
+
+        </LinearLayout>
+
+    </FrameLayout>
+
+</android.support.v17.leanback.app.GuidedStepRootLayout>
diff --git a/v17/leanback/res/values/attrs.xml b/v17/leanback/res/values/attrs.xml
index ebffa93..cde7a2d 100644
--- a/v17/leanback/res/values/attrs.xml
+++ b/v17/leanback/res/values/attrs.xml
@@ -318,6 +318,11 @@
              can set this to <code>@style/Theme.Leanback.GuidedStep</code> in order to specify the
              default GuidedStepFragment styles. -->
         <attr name="guidedStepTheme" format="reference" />
+        <!-- Used to control the height of the fragment. By default this fragment will take
+             up the full height of it's parent. The height of this fragment is governed by
+             this property. Default weight is set to 2.0, so inorder to render the fragment
+             in half screen mode, this attribute should be set to 1.0 -->
+        <attr name="guidedStepHeightWeight" format="float" />
 
         <!-- @hide
              Theme attribute used to inspect theme inheritance. -->
@@ -475,7 +480,6 @@
         <attr name="guidedActionContentWidth" format="reference" />
         <!-- Deprecated theme attribute, do not use -->
         <attr name="guidedActionContentWidthNoIcon" format="reference" />
-
     </declare-styleable>
 
     <declare-styleable name="lbDatePicker">
@@ -485,4 +489,4 @@
         <attr name="datePickerFormat" format="string"/>
     </declare-styleable>
 
-</resources>
\ No newline at end of file
+</resources>
diff --git a/v17/leanback/res/values/dimens.xml b/v17/leanback/res/values/dimens.xml
index 87b3549..5823d2b 100644
--- a/v17/leanback/res/values/dimens.xml
+++ b/v17/leanback/res/values/dimens.xml
@@ -237,6 +237,8 @@
     <dimen name="lb_guidedactions_elevation">12dp</dimen>
     <dimen name="lb_guidedactions_vertical_padding">12dp</dimen>
 
+    <item name="lb_guidedstep_height_weight" format="float" type="string">2.0</item>
+    <item name="lb_guidedstep_height_weight_translucent" format="float" type="string">1.0</item>
     <item name="lb_guidedactions_item_disabled_text_alpha" format="float" type="string">0.25</item>
     <item name="lb_guidedactions_item_disabled_description_text_alpha" format="float" type="string">0.25</item>
     <item name="lb_guidedactions_item_unselected_text_alpha" format="float" type="string">1.00</item>
diff --git a/v17/leanback/res/values/styles.xml b/v17/leanback/res/values/styles.xml
index 1f7f3dd..6d7c74b 100644
--- a/v17/leanback/res/values/styles.xml
+++ b/v17/leanback/res/values/styles.xml
@@ -384,6 +384,7 @@
 
     <!-- Style for the title view in a GuidanceStylist's default layout. -->
     <style name="Widget.Leanback.GuidanceTitleStyle">
+        <item name="android:importantForAccessibility">no</item>
         <item name="android:layout_toStartOf">@id/guidance_icon</item>
         <item name="android:layout_width">wrap_content</item>
         <item name="android:layout_height">wrap_content</item>
@@ -402,6 +403,7 @@
 
     <!-- Style for the description view in a GuidanceStylist's default layout. -->
     <style name="Widget.Leanback.GuidanceDescriptionStyle">
+        <item name="android:importantForAccessibility">no</item>
         <item name="android:layout_below">@id/guidance_title</item>
         <item name="android:layout_toStartOf">@id/guidance_icon</item>
         <item name="android:layout_width">wrap_content</item>
@@ -419,6 +421,7 @@
 
     <!-- Style for the breadcrumb view in a GuidanceStylist's default layout. -->
     <style name="Widget.Leanback.GuidanceBreadcrumbStyle">
+        <item name="android:importantForAccessibility">no</item>
         <item name="android:layout_above">@id/guidance_title</item>
         <item name="android:layout_toStartOf">@id/guidance_icon</item>
         <item name="android:layout_width">wrap_content</item>
diff --git a/v17/leanback/res/values/themes.xml b/v17/leanback/res/values/themes.xml
index 42b49bc..1c52769 100644
--- a/v17/leanback/res/values/themes.xml
+++ b/v17/leanback/res/values/themes.xml
@@ -121,6 +121,7 @@
 
     <style name="Theme.Leanback.GuidedStep" parent="Theme.LeanbackBase">
         <item name="guidedStepThemeFlag">true</item>
+        <item name="guidedStepHeightWeight">@string/lb_guidedstep_height_weight</item>
 
         <item name="android:windowEnterTransition">@transition/lb_guidedstep_activity_enter</item>
 
@@ -169,4 +170,9 @@
         <item name="guidedActionVerticalPadding">@dimen/lb_guidedactions_vertical_padding</item>
     </style>
 
+    <style name="Theme.Leanback.GuidedStep.Half" parent="Theme.Leanback.GuidedStep">
+      <item name="guidedStepHeightWeight">@string/lb_guidedstep_height_weight_translucent</item>
+      <item name="android:windowIsTranslucent">true</item>
+      <item name="android:windowBackground">@android:color/transparent</item>
+    </style>
 </resources>
diff --git a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java
index 604fcc1..515946c 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java
@@ -25,8 +25,8 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
-import android.support.v17.leanback.transition.TransitionHelper;
 import android.support.v17.leanback.R;
+import android.support.v17.leanback.transition.TransitionHelper;
 import android.support.v17.leanback.widget.GuidanceStylist;
 import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
 import android.support.v17.leanback.widget.GuidedAction;
@@ -46,8 +46,8 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
+import android.widget.FrameLayout;
 import android.widget.ImageView;
-import android.widget.RelativeLayout;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
@@ -582,7 +582,6 @@
     public static int addAsRoot(Activity activity, GuidedStepFragment fragment, int id) {
         // Workaround b/23764120: call getDecorView() to force requestFeature of ActivityTransition.
         activity.getWindow().getDecorView();
-
         FragmentManager fragmentManager = activity.getFragmentManager();
         FragmentTransaction ft = fragmentManager.beginTransaction();
         fragment.setUiStyle(UI_STYLE_ACTIVITY_ROOT);
@@ -965,8 +964,10 @@
 
         GuidedStepRootLayout root = (GuidedStepRootLayout) inflater.inflate(
                 R.layout.lb_guidedstep_fragment, container, false);
+
         root.setFocusOutStart(isFocusOutStartAllowed());
         root.setFocusOutEnd(isFocusOutEndAllowed());
+
         ViewGroup guidanceContainer = (ViewGroup) root.findViewById(R.id.content_fragment);
         ViewGroup actionContainer = (ViewGroup) root.findViewById(R.id.action_fragment);
 
@@ -1031,6 +1032,7 @@
         mAdapterGroup.addAdpter(mAdapter, mButtonAdapter);
         mAdapterGroup.addAdpter(mSubAdapter, null);
         mAdapterGroup.setEditListener(editListener);
+        mActionsStylist.setEditListener(editListener);
 
         mActionsStylist.getActionsGridView().setAdapter(mAdapter);
         if (mActionsStylist.getSubActionsGridView() != null) {
@@ -1066,9 +1068,12 @@
 
         setSelectedButtonActionPosition(0);
 
+        // Add the background view.
         View backgroundView = onCreateBackgroundView(inflater, root, savedInstanceState);
         if (backgroundView != null) {
-            root.addView(backgroundView, 0);
+            FrameLayout backgroundViewRoot = (FrameLayout)root.findViewById(
+                R.id.guidedstep_background_view_root);
+            backgroundViewRoot.addView(backgroundView, 0);
         }
         return root;
     }
diff --git a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepRootLayout.java b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepRootLayout.java
index 9fc3720..e644636 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepRootLayout.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepRootLayout.java
@@ -18,13 +18,13 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewParent;
-import android.widget.FrameLayout;
+import android.widget.LinearLayout;
 
 /**
  * Utility class used by GuidedStepFragment to disable focus out left/right.
  * @hide
  */
-class GuidedStepRootLayout extends FrameLayout {
+class GuidedStepRootLayout extends LinearLayout {
 
     private boolean mFocusOutStart = false;
     private boolean mFocusOutEnd = false;
diff --git a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepSupportFragment.java
index b4110e9..bedd570 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepSupportFragment.java
@@ -27,8 +27,8 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
-import android.support.v17.leanback.transition.TransitionHelper;
 import android.support.v17.leanback.R;
+import android.support.v17.leanback.transition.TransitionHelper;
 import android.support.v17.leanback.widget.GuidanceStylist;
 import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
 import android.support.v17.leanback.widget.GuidedAction;
@@ -48,8 +48,8 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
+import android.widget.FrameLayout;
 import android.widget.ImageView;
-import android.widget.RelativeLayout;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
@@ -584,7 +584,6 @@
     public static int addAsRoot(FragmentActivity activity, GuidedStepSupportFragment fragment, int id) {
         // Workaround b/23764120: call getDecorView() to force requestFeature of ActivityTransition.
         activity.getWindow().getDecorView();
-
         FragmentManager fragmentManager = activity.getSupportFragmentManager();
         FragmentTransaction ft = fragmentManager.beginTransaction();
         fragment.setUiStyle(UI_STYLE_ACTIVITY_ROOT);
@@ -967,8 +966,10 @@
 
         GuidedStepRootLayout root = (GuidedStepRootLayout) inflater.inflate(
                 R.layout.lb_guidedstep_fragment, container, false);
+
         root.setFocusOutStart(isFocusOutStartAllowed());
         root.setFocusOutEnd(isFocusOutEndAllowed());
+
         ViewGroup guidanceContainer = (ViewGroup) root.findViewById(R.id.content_fragment);
         ViewGroup actionContainer = (ViewGroup) root.findViewById(R.id.action_fragment);
 
@@ -1033,6 +1034,7 @@
         mAdapterGroup.addAdpter(mAdapter, mButtonAdapter);
         mAdapterGroup.addAdpter(mSubAdapter, null);
         mAdapterGroup.setEditListener(editListener);
+        mActionsStylist.setEditListener(editListener);
 
         mActionsStylist.getActionsGridView().setAdapter(mAdapter);
         if (mActionsStylist.getSubActionsGridView() != null) {
@@ -1068,9 +1070,12 @@
 
         setSelectedButtonActionPosition(0);
 
+        // Add the background view.
         View backgroundView = onCreateBackgroundView(inflater, root, savedInstanceState);
         if (backgroundView != null) {
-            root.addView(backgroundView, 0);
+            FrameLayout backgroundViewRoot = (FrameLayout)root.findViewById(
+                R.id.guidedstep_background_view_root);
+            backgroundViewRoot.addView(backgroundView, 0);
         }
         return root;
     }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidanceStylist.java b/v17/leanback/src/android/support/v17/leanback/widget/GuidanceStylist.java
index 3fcdbba..7284a53 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GuidanceStylist.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GuidanceStylist.java
@@ -20,6 +20,7 @@
 import android.graphics.drawable.Drawable;
 import android.support.annotation.NonNull;
 import android.support.v17.leanback.R;
+import android.text.TextUtils;
 import android.util.TypedValue;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -154,6 +155,7 @@
         mBreadcrumbView = (TextView) guidanceView.findViewById(R.id.guidance_breadcrumb);
         mDescriptionView = (TextView) guidanceView.findViewById(R.id.guidance_description);
         mIconView = (ImageView) guidanceView.findViewById(R.id.guidance_icon);
+        View guidanceContainer = guidanceView.findViewById(R.id.guidance_container);
 
         // We allow any of the cached subviews to be null, so that subclasses can choose not to
         // display a particular piece of information.
@@ -169,6 +171,16 @@
         if (mIconView != null) {
             mIconView.setImageDrawable(guidance.getIconDrawable());
         }
+        if (guidanceContainer != null) {
+            CharSequence contentDescription = guidanceContainer.getContentDescription();
+            if (TextUtils.isEmpty(contentDescription)) {
+                guidanceContainer.setContentDescription(new StringBuilder()
+                        .append(guidance.getBreadcrumb()).append('\n')
+                        .append(guidance.getTitle()).append('\n')
+                        .append(guidance.getDescription())
+                        .toString());
+            }
+        }
         return guidanceView;
     }
 
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 c5bf7a5..977c201 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GuidedAction.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GuidedAction.java
@@ -38,7 +38,7 @@
  * user input on selection, in which case they will be displayed with a chevron indicator.
  * <p>
  * GuidedAction recommends to use {@link Builder}. When application subclass GuidedAction, it
- * can subclass {@link BuilderBase}, implement {@link BuilderBase#build()} where it should
+ * can subclass {@link BuilderBase}, implement its own builder() method where it should
  * call {@link BuilderBase#applyValues(GuidedAction)}.
  */
 public class GuidedAction extends Action {
@@ -97,12 +97,17 @@
      */
     public static final long ACTION_ID_NO = -9;
 
+    static final int EDITING_NONE = 0;
+    static final int EDITING_TITLE = 1;
+    static final int EDITING_DESCRIPTION = 2;
+    static final int EDITING_ACTIVATOR_VIEW = 3;
+
     /**
      * Base builder class to build a {@link GuidedAction} object.  When subclass GuidedAction, you
-     * can override this BuilderBase class, implements {@link #build()} and call
+     * can override this BuilderBase class, implements your build() method which should call
      * {@link #applyValues(GuidedAction)}.  When using GuidedAction directly, use {@link Builder}.
      */
-    public abstract static class BuilderBase<T extends GuidedAction> {
+    public abstract static class BuilderBase<B extends BuilderBase> {
         private Context mContext;
         private long mId;
         private CharSequence mTitle;
@@ -114,8 +119,7 @@
         private boolean mMultilineDescription;
         private boolean mHasNext;
         private boolean mInfoOnly;
-        private boolean mEditable = false;
-        private boolean mDescriptionEditable = false;
+        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;
@@ -143,12 +147,6 @@
         }
 
         /**
-         * Builds the GuidedAction corresponding to this Builder.
-         * @return the GuidedAction as configured through this BuilderBase.
-         */
-        public abstract T build();
-
-        /**
          * Subclass of BuilderBase should call this function to apply values.
          * @param action GuidedAction to apply BuilderBase values.
          */
@@ -164,7 +162,6 @@
             // Subclass values
             action.mIntent = mIntent;
             action.mEditable = mEditable;
-            action.mDescriptionEditable = mDescriptionEditable;
             action.mInputType = mInputType;
             action.mDescriptionInputType = mDescriptionInputType;
             action.mEditInputType = mEditInputType;
@@ -187,7 +184,7 @@
          * {@link GuidedAction#ACTION_ID_YES} {@link GuidedAction#ACTION_ID_NO}.
          * @return The same BuilderBase object.
          */
-        public BuilderBase<T> clickAction(long id) {
+        public B clickAction(long id) {
             if (id == ACTION_ID_OK) {
                 mId = ACTION_ID_OK;
                 mTitle = mContext.getString(android.R.string.ok);
@@ -207,7 +204,7 @@
                 mId = ACTION_ID_NO;
                 mTitle = mContext.getString(android.R.string.no);
             }
-            return this;
+            return (B) this;
         }
 
         /**
@@ -215,9 +212,9 @@
          * it is typically used to determine what to do when an action is clicked.
          * @param id The ID to associate with this action.
          */
-        public BuilderBase<T> id(long id) {
+        public B id(long id) {
             mId = id;
-            return this;
+            return (B) this;
         }
 
         /**
@@ -225,9 +222,9 @@
          * action to be taken on click, e.g. "Continue" or "Cancel".
          * @param title The title for this action.
          */
-        public BuilderBase<T> title(CharSequence title) {
+        public B title(CharSequence title) {
             mTitle = title;
-            return this;
+            return (B) this;
         }
 
         /**
@@ -235,9 +232,9 @@
          * action to be taken on click, e.g. "Continue" or "Cancel".
          * @param titleResourceId The resource id of title for this action.
          */
-        public BuilderBase<T> title(@StringRes int titleResourceId) {
+        public B title(@StringRes int titleResourceId) {
             mTitle = getContext().getString(titleResourceId);
-            return this;
+            return (B) this;
         }
 
         /**
@@ -245,9 +242,9 @@
          * replaces the string of title.
          * @param editTitle The optional title text to edit when TextView is activated.
          */
-        public BuilderBase<T> editTitle(CharSequence editTitle) {
+        public B editTitle(CharSequence editTitle) {
             mEditTitle = editTitle;
-            return this;
+            return (B) this;
         }
 
         /**
@@ -256,9 +253,9 @@
          * @param editTitleResourceId String resource id of the optional title text to edit when
          * TextView is activated.
          */
-        public BuilderBase<T> editTitle(@StringRes int editTitleResourceId) {
+        public B editTitle(@StringRes int editTitleResourceId) {
             mEditTitle = getContext().getString(editTitleResourceId);
-            return this;
+            return (B) this;
         }
 
         /**
@@ -266,9 +263,9 @@
          * providing extra information on what the action will do.
          * @param description The description for this action.
          */
-        public BuilderBase<T> description(CharSequence description) {
+        public B description(CharSequence description) {
             mDescription = description;
-            return this;
+            return (B) this;
         }
 
         /**
@@ -276,9 +273,9 @@
          * providing extra information on what the action will do.
          * @param descriptionResourceId String resource id of the description for this action.
          */
-        public BuilderBase<T> description(@StringRes int descriptionResourceId) {
+        public B description(@StringRes int descriptionResourceId) {
             mDescription = getContext().getString(descriptionResourceId);
-            return this;
+            return (B) this;
         }
 
         /**
@@ -286,9 +283,9 @@
          * description replaces the string of description.
          * @param description The description to edit for this action.
          */
-        public BuilderBase<T> editDescription(CharSequence description) {
+        public B editDescription(CharSequence description) {
             mEditDescription = description;
-            return this;
+            return (B) this;
         }
 
         /**
@@ -297,9 +294,9 @@
          * @param descriptionResourceId String resource id of the description to edit for this
          * action.
          */
-        public BuilderBase<T> editDescription(@StringRes int descriptionResourceId) {
+        public B editDescription(@StringRes int descriptionResourceId) {
             mEditDescription = getContext().getString(descriptionResourceId);
-            return this;
+            return (B) this;
         }
 
         /**
@@ -307,18 +304,18 @@
          * directly when the action is clicked.
          * @param intent The intent associated with this action.
          */
-        public BuilderBase<T> intent(Intent intent) {
+        public B intent(Intent intent) {
             mIntent = intent;
-            return this;
+            return (B) this;
         }
 
         /**
          * Sets the action's icon drawable.
          * @param icon The drawable for the icon associated with this action.
          */
-        public BuilderBase<T> icon(Drawable icon) {
+        public B icon(Drawable icon) {
             mIcon = icon;
-            return this;
+            return (B) this;
         }
 
         /**
@@ -330,7 +327,7 @@
          * @deprecated Use {@link #icon(int)}.
          */
         @Deprecated
-        public BuilderBase<T> iconResourceId(@DrawableRes int iconResourceId, Context context) {
+        public B iconResourceId(@DrawableRes int iconResourceId, Context context) {
             return icon(ContextCompat.getDrawable(context, iconResourceId));
         }
 
@@ -340,7 +337,7 @@
          * {@link #icon(Drawable)}.
          * @param iconResourceId The resource ID for the icon associated with this action.
          */
-        public BuilderBase<T> icon(@DrawableRes int iconResourceId) {
+        public B icon(@DrawableRes int iconResourceId) {
             return icon(ContextCompat.getDrawable(getContext(), iconResourceId));
         }
 
@@ -349,24 +346,48 @@
          * checked, or belong to a check set.
          * @param editable Whether this action is editable.
          */
-        public BuilderBase<T> editable(boolean editable) {
-            mEditable = editable;
+        public B editable(boolean editable) {
+            if (!editable && mEditable == EDITING_TITLE) {
+                mEditable = EDITING_NONE;
+                return (B) this;
+            }
+            mEditable = EDITING_TITLE;
             if (mChecked || mCheckSetId != NO_CHECK_SET) {
                 throw new IllegalArgumentException("Editable actions cannot also be checked");
             }
-            return this;
+            return (B) this;
         }
 
         /**
          * Indicates whether this action's description is editable
          * @param editable Whether this action description is editable.
          */
-        public BuilderBase<T> descriptionEditable(boolean editable) {
-            mDescriptionEditable = editable;
+        public B descriptionEditable(boolean editable) {
+            if (!editable && mEditable == EDITING_DESCRIPTION) {
+                mEditable = EDITING_NONE;
+                return (B) this;
+            }
+            mEditable = EDITING_DESCRIPTION;
             if (mChecked || mCheckSetId != NO_CHECK_SET) {
                 throw new IllegalArgumentException("Editable actions cannot also be checked");
             }
-            return this;
+            return (B) this;
+        }
+
+        /**
+         * Indicates whether this action has a view can be activated to edit, e.g. a DatePicker.
+         * @param editable Whether this action has view can be activated to edit.
+         */
+        public B hasEditableActivatorView(boolean editable) {
+            if (!editable && mEditable == EDITING_ACTIVATOR_VIEW) {
+                mEditable = EDITING_NONE;
+                return (B) this;
+            }
+            mEditable = EDITING_ACTIVATOR_VIEW;
+            if (mChecked || mCheckSetId != NO_CHECK_SET) {
+                throw new IllegalArgumentException("Editable actions cannot also be checked");
+            }
+            return (B) this;
         }
 
         /**
@@ -374,9 +395,9 @@
          *
          * @param inputType InputType for the action title not in editing.
          */
-        public BuilderBase<T> inputType(int inputType) {
+        public B inputType(int inputType) {
             mInputType = inputType;
-            return this;
+            return (B) this;
         }
 
         /**
@@ -384,9 +405,9 @@
          *
          * @param inputType InputType for the action description not in editing.
          */
-        public BuilderBase<T> descriptionInputType(int inputType) {
+        public B descriptionInputType(int inputType) {
             mDescriptionInputType = inputType;
-            return this;
+            return (B) this;
         }
 
 
@@ -395,9 +416,9 @@
          *
          * @param inputType InputType for the action title in editing.
          */
-        public BuilderBase<T> editInputType(int inputType) {
+        public B editInputType(int inputType) {
             mEditInputType = inputType;
-            return this;
+            return (B) this;
         }
 
         /**
@@ -405,9 +426,9 @@
          *
          * @param inputType InputType for the action description in editing.
          */
-        public BuilderBase<T> descriptionEditInputType(int inputType) {
+        public B descriptionEditInputType(int inputType) {
             mDescriptionEditInputType = inputType;
-            return this;
+            return (B) this;
         }
 
 
@@ -415,12 +436,12 @@
          * Indicates whether this action is initially checked.
          * @param checked Whether this action is checked.
          */
-        public BuilderBase<T> checked(boolean checked) {
+        public B checked(boolean checked) {
             mChecked = checked;
-            if (mEditable || mDescriptionEditable) {
+            if (mEditable != EDITING_NONE) {
                 throw new IllegalArgumentException("Editable actions cannot also be checked");
             }
-            return this;
+            return (B) this;
         }
 
         /**
@@ -430,12 +451,12 @@
          * @param checkSetId The check set ID, or {@link GuidedAction#NO_CHECK_SET} to indicate not
          * radio or checkbox, or {@link GuidedAction#CHECKBOX_CHECK_SET_ID} to indicate a checkbox.
          */
-        public BuilderBase<T> checkSetId(int checkSetId) {
+        public B checkSetId(int checkSetId) {
             mCheckSetId = checkSetId;
-            if (mEditable || mDescriptionEditable) {
+            if (mEditable != EDITING_NONE) {
                 throw new IllegalArgumentException("Editable actions cannot also be in check sets");
             }
-            return this;
+            return (B) this;
         }
 
         /**
@@ -443,36 +464,36 @@
          * appropriately.
          * @param multilineDescription Whether this action has a multiline description.
          */
-        public BuilderBase<T> multilineDescription(boolean multilineDescription) {
+        public B multilineDescription(boolean multilineDescription) {
             mMultilineDescription = multilineDescription;
-            return this;
+            return (B) this;
         }
 
         /**
          * Indicates whether this action has a next state and should display a chevron.
          * @param hasNext Whether this action has a next state.
          */
-        public BuilderBase<T> hasNext(boolean hasNext) {
+        public B hasNext(boolean hasNext) {
             mHasNext = hasNext;
-            return this;
+            return (B) this;
         }
 
         /**
          * Indicates whether this action is for information purposes only and cannot be clicked.
          * @param infoOnly Whether this action has a next state.
          */
-        public BuilderBase<T> infoOnly(boolean infoOnly) {
+        public B infoOnly(boolean infoOnly) {
             mInfoOnly = infoOnly;
-            return this;
+            return (B) this;
         }
 
         /**
          * Indicates whether this action is enabled.  If not enabled, an action cannot be clicked.
          * @param enabled Whether the action is enabled.
          */
-        public BuilderBase<T> enabled(boolean enabled) {
+        public B enabled(boolean enabled) {
             mEnabled = enabled;
-            return this;
+            return (B) this;
         }
 
         /**
@@ -480,9 +501,9 @@
          * @param focusable
          * @return The same BuilderBase object.
          */
-        public BuilderBase<T> focusable(boolean focusable) {
+        public B focusable(boolean focusable) {
             mFocusable = focusable;
-            return this;
+            return (B) this;
         }
 
         /**
@@ -490,16 +511,16 @@
          * @param subActions
          * @return The same BuilderBase object.
          */
-        public BuilderBase<T> subActions(List<GuidedAction> subActions) {
+        public B subActions(List<GuidedAction> subActions) {
             mSubActions = subActions;
-            return this;
+            return (B) this;
         }
     }
 
     /**
      * Builds a {@link GuidedAction} object.
      */
-    public static class Builder extends BuilderBase<GuidedAction> {
+    public static class Builder extends BuilderBase<Builder> {
 
         /**
          * @deprecated Use {@link GuidedAction.Builder#GuidedAction.Builder(Context)}.
@@ -517,7 +538,10 @@
             super(context);
         }
 
-        @Override
+        /**
+         * Builds the GuidedAction corresponding to this Builder.
+         * @return The GuidedAction as configured through this Builder.
+         */
         public GuidedAction build() {
             GuidedAction action = new GuidedAction();
             applyValues(action);
@@ -528,8 +552,7 @@
 
     private CharSequence mEditTitle;
     private CharSequence mEditDescription;
-    private boolean mEditable;
-    private boolean mDescriptionEditable;
+    private int mEditable;
     private int mInputType;
     private int mDescriptionInputType;
     private int mEditInputType;
@@ -642,11 +665,20 @@
     }
 
     /**
+     * Returns whether this action has any editable part, e.g. editable title, editable description
+     * or editable activate view.
+     * @return true if this action has any editable part, false otherwise.
+     */
+    public boolean hasAnyEditable() {
+        return mEditable != EDITING_NONE;
+    }
+
+    /**
      * Returns whether this action title is editable.
      * @return true if the action title is editable, false otherwise.
      */
     public boolean isEditable() {
-        return mEditable;
+        return mEditable == EDITING_TITLE;
     }
 
     /**
@@ -654,7 +686,23 @@
      * @return true if the action description is editable, false otherwise.
      */
     public boolean isDescriptionEditable() {
-        return mDescriptionEditable;
+        return mEditable == EDITING_DESCRIPTION;
+    }
+
+    /**
+     * Returns if this action has editable title or editable description.
+     * @return True if this action has editable title or editable description, false otherwise.
+     */
+    public boolean hasTextEditable() {
+        return mEditable == EDITING_TITLE || mEditable == EDITING_DESCRIPTION;
+    }
+
+    /**
+     * Returns whether this action can be activated to edit, e.g. a DatePicker.
+     * @return true if the action can be activated to edit.
+     */
+    public boolean hasEditableActivatorView() {
+        return mEditable == EDITING_ACTIVATOR_VIEW;
     }
 
     /**
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapter.java b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapter.java
index e0fad0a..ee44656 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapter.java
@@ -108,9 +108,13 @@
                 GuidedActionsStylist.ViewHolder avh = (GuidedActionsStylist.ViewHolder)
                         getRecyclerView().getChildViewHolder(v);
                 GuidedAction action = avh.getAction();
-                if (action.isEditable() || action.isDescriptionEditable()) {
+                if (action.hasTextEditable()) {
                     if (DEBUG_EDIT) Log.v(TAG_EDIT, "openIme by click");
                     mGroup.openIme(GuidedActionAdapter.this, avh);
+                } else if (action.hasEditableActivatorView()) {
+                    if (DEBUG_EDIT) Log.v(TAG_EDIT, "toggle editing mode by click");
+                    getGuidedActionsStylist().setEditingMode(avh, avh.getAction(),
+                            !avh.isInEditingActivatorView());
                 } else {
                     handleCheckedActions(avh);
                     if (action.isEnabled() && !action.infoOnly()) {
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapterGroup.java b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapterGroup.java
index 858a0ed..6fa3d89 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapterGroup.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapterGroup.java
@@ -95,7 +95,7 @@
                         (GuidedActionsStylist.ViewHolder) adapter.getGuidedActionsStylist()
                                 .getActionsGridView().findViewHolderForPosition(index);
                 if (vh != null) {
-                    if (vh.getAction().isEditable() || vh.getAction().isDescriptionEditable()) {
+                    if (vh.getAction().hasTextEditable()) {
                         if (DEBUG_EDIT) Log.v(TAG_EDIT, "openIme of next Action");
                         // open Ime on next action.
                         openIme(adapter, vh);
@@ -122,7 +122,7 @@
     public void openIme(GuidedActionAdapter adapter, GuidedActionsStylist.ViewHolder avh) {
         adapter.getGuidedActionsStylist().setEditingMode(avh, avh.getAction(), true);
         View v = avh.getEditingView();
-        if (v == null) {
+        if (v == null || !avh.isInEditingText()) {
             return;
         }
         InputMethodManager mgr = (InputMethodManager)
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionsStylist.java b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionsStylist.java
index 2a95dfe..b39e2f5 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionsStylist.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionsStylist.java
@@ -30,7 +30,10 @@
 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.GuidedActionAdapter.EditListener;
 import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v17.leanback.widget.picker.DatePicker;
+import android.support.v17.leanback.widget.picker.Picker;
 import android.support.v4.content.ContextCompat;
 import android.support.v4.view.ViewCompat;
 import android.support.v7.widget.RecyclerView;
@@ -39,30 +42,40 @@
 import android.util.Log;
 import android.util.TypedValue;
 import android.view.animation.DecelerateInterpolator;
+import android.view.animation.LinearInterpolator;
 import android.view.inputmethod.EditorInfo;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.View.AccessibilityDelegate;
 import android.view.ViewGroup;
 import android.view.ViewGroup.LayoutParams;
 import android.view.ViewPropertyAnimator;
 import android.view.ViewTreeObserver;
 import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.Checkable;
 import android.widget.EditText;
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import java.util.Calendar;
 import java.util.Collections;
 import java.util.List;
 
+import static android.support.v17.leanback.widget.GuidedAction.EDITING_NONE;
+import static android.support.v17.leanback.widget.GuidedAction.EDITING_TITLE;
+import static android.support.v17.leanback.widget.GuidedAction.EDITING_DESCRIPTION;
+import static android.support.v17.leanback.widget.GuidedAction.EDITING_ACTIVATOR_VIEW;
+
 /**
  * GuidedActionsStylist is used within a {@link android.support.v17.leanback.app.GuidedStepFragment}
  * to supply the right-side panel where users can take actions. It consists of a container for the
  * list of actions, and a stationary selector view that indicates visually the location of focus.
  * GuidedActionsStylist has two different layouts: default is for normal actions including text,
- * radio, checkbox etc, the other when {@link #setAsButtonActions()} is called is recommended for
- * button actions such as "yes", "no".
+ * radio, checkbox, DatePicker, etc, the other when {@link #setAsButtonActions()} is called is
+ * recommended for button actions such as "yes", "no".
  * <p>
  * Many aspects of the base GuidedActionsStylist can be customized through theming; see the
  * theme attributes below. Note that these attributes are not set on individual elements in layout
@@ -72,8 +85,16 @@
  * <p>
  * If these hooks are insufficient, this class may also be subclassed. Subclasses may wish to
  * override the {@link #onProvideLayoutId} method to change the layout used to display the
- * list container and selector, or the {@link #onProvideItemLayoutId} method to change the layout
- * used to display each action.
+ * list container and selector; override {@link #onProvideItemLayoutId(int)} and
+ * {@link #getItemViewType(GuidedAction)} method to change the layout used to display each action.
+ * <p>
+ * To support a "click to activate" view similar to DatePicker, app needs:
+ * <li> Override {@link #onProvideItemLayoutId(int)} and {@link #getItemViewType(GuidedAction)},
+ * provides a layout id for the action.
+ * <li> The layout must include a widget with id "guidedactions_activator_item", the widget is
+ * toggled edit mode by {@link View#setActivated(boolean)}.
+ * <li> Override {@link #onBindActivatorView(ViewHolder, GuidedAction)} to populate values into View.
+ * <li> Override {@link #onUpdateActivatorView(ViewHolder, GuidedAction)} to update action.
  * <p>
  * Note: If an alternate list layout is provided, the following view IDs must be supplied:
  * <ul>
@@ -136,6 +157,11 @@
     public static final int VIEW_TYPE_DEFAULT = 0;
 
     /**
+     * ViewType for DatePicker.
+     */
+    public static final int VIEW_TYPE_DATE_PICKER = 1;
+
+    /**
      * ViewHolder caches information about the action item layouts' subviews. Subclasses of {@link
      * GuidedActionsStylist} may also wish to subclass this in order to add fields.
      * @see GuidedAction
@@ -146,13 +172,29 @@
         private View mContentView;
         private TextView mTitleView;
         private TextView mDescriptionView;
+        private View mActivatorView;
         private ImageView mIconView;
         private ImageView mCheckmarkView;
         private ImageView mChevronView;
-        private boolean mInEditing;
-        private boolean mInEditingDescription;
+        private int mEditingMode = EDITING_NONE;
         private final boolean mIsSubAction;
 
+        final AccessibilityDelegate mDelegate = new AccessibilityDelegate() {
+            @Override
+            public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
+                super.onInitializeAccessibilityEvent(host, event);
+                event.setChecked(mAction != null && mAction.isChecked());
+            }
+
+            @Override
+            public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+                super.onInitializeAccessibilityNodeInfo(host, info);
+                info.setCheckable(
+                        mAction != null && mAction.getCheckSetId() != GuidedAction.NO_CHECK_SET);
+                info.setChecked(mAction != null && mAction.isChecked());
+            }
+        };
+
         /**
          * Constructs an ViewHolder and caches the relevant subviews.
          */
@@ -168,11 +210,14 @@
 
             mContentView = v.findViewById(R.id.guidedactions_item_content);
             mTitleView = (TextView) v.findViewById(R.id.guidedactions_item_title);
+            mActivatorView = v.findViewById(R.id.guidedactions_activator_item);
             mDescriptionView = (TextView) v.findViewById(R.id.guidedactions_item_description);
             mIconView = (ImageView) v.findViewById(R.id.guidedactions_item_icon);
             mCheckmarkView = (ImageView) v.findViewById(R.id.guidedactions_item_checkmark);
             mChevronView = (ImageView) v.findViewById(R.id.guidedactions_item_chevron);
             mIsSubAction = isSubAction;
+
+            v.setAccessibilityDelegate(mDelegate);
         }
 
         /**
@@ -235,26 +280,56 @@
         }
 
         /**
-         * Returns true if the TextView is in editing title or description, false otherwise.
+         * Returns true if in editing title, description, or activator View, false otherwise.
          */
         public boolean isInEditing() {
-            return mInEditing;
+            return mEditingMode != EDITING_NONE;
+        }
+
+        /**
+         * Returns true if in editing title, description, so IME would be open.
+         * @return True if in editing title, description, so IME would be open, false otherwise.
+         */
+        public boolean isInEditingText() {
+            return mEditingMode == EDITING_TITLE || mEditingMode == EDITING_DESCRIPTION;
+        }
+
+        /**
+         * Returns true if the TextView is in editing title, false otherwise.
+         */
+        public boolean isInEditingTitle() {
+            return mEditingMode == EDITING_TITLE;
         }
 
         /**
          * Returns true if the TextView is in editing description, false otherwise.
          */
         public boolean isInEditingDescription() {
-            return mInEditingDescription;
+            return mEditingMode == EDITING_DESCRIPTION;
         }
 
         /**
-         * @return Current editing title view or description view or null if not in editing.
+         * Returns true if is in editing activator view with id guidedactions_activator_item, false
+         * otherwise.
+         */
+        public boolean isInEditingActivatorView() {
+            return mEditingMode == EDITING_ACTIVATOR_VIEW;
+        }
+
+        /**
+         * @return Current editing title view or description view or activator view or null if not
+         * in editing.
          */
         public View getEditingView() {
-            if (mInEditing) {
-                return mInEditingDescription ?  mDescriptionView : mTitleView;
-            } else {
+            switch(mEditingMode) {
+            case EDITING_TITLE:
+                return mTitleView;
+            case EDITING_DESCRIPTION:
+                return mDescriptionView;
+            case EDITING_ACTIVATOR_VIEW:
+                return mActivatorView;
+            case EDITING_NONE:
+            default:
                 return null;
             }
         }
@@ -297,6 +372,8 @@
     private int mVerticalPadding;
     private int mDisplayHeight;
 
+    private EditListener mEditListener;
+
     private GuidedAction mExpandedAction = null;
     private Object mExpandTransition;
 
@@ -428,6 +505,9 @@
      * @return View type that used in {@link #onProvideItemLayoutId(int)}.
      */
     public int getItemViewType(GuidedAction action) {
+        if (action instanceof GuidedDatePickerAction) {
+            return VIEW_TYPE_DATE_PICKER;
+        }
         return VIEW_TYPE_DEFAULT;
     }
 
@@ -451,19 +531,24 @@
     /**
      * Provides the resource ID of the layout defining the view for an individual guided actions.
      * Subclasses may override to provide their own customized layouts. The base implementation
-     * returns {@link android.support.v17.leanback.R.layout#lb_guidedactions_item}. If overridden,
-     * the substituted layout should contain matching IDs for any views that should be managed by
-     * the base class; this can be achieved by starting with a copy of the base layout file. Note
-     * that in order for the item to support editing, the title view should both subclass {@link
-     * android.widget.EditText} and implement {@link ImeKeyMonitor}; see {@link
-     * GuidedActionEditText}.
+     * supports:
+     * <li>{@link android.support.v17.leanback.R.layout#lb_guidedactions_item}
+     * <li>{{@link android.support.v17.leanback.R.layout#lb_guidedactions_datepicker_item}. If
+     * overridden, the substituted layout should contain matching IDs for any views that should be
+     * managed by the base class; this can be achieved by starting with a copy of the base layout
+     * file. Note that in order for the item to support editing, the title view should both subclass
+     * {@link android.widget.EditText} and implement {@link ImeKeyMonitor}; see
+     * {@link GuidedActionEditText}.
+     *
      * @param viewType View type returned by {@link #getItemViewType(GuidedAction)}
      * @return The resource ID of the layout to be inflated to define the view to display an
-     * individual GuidedAction.
+     *         individual GuidedAction.
      */
     public int onProvideItemLayoutId(int viewType) {
         if (viewType == VIEW_TYPE_DEFAULT) {
             return onProvideItemLayoutId();
+        } else if (viewType == VIEW_TYPE_DATE_PICKER) {
+            return R.layout.lb_guidedactions_datepicker_item;
         } else {
             throw new RuntimeException("ViewType " + viewType +
                     " not supported in GuidedActionsStylist");
@@ -549,6 +634,9 @@
                 vh.mDescriptionView.setMaxLines(mDescriptionMinLines);
             }
         }
+        if (vh.mActivatorView != null) {
+            onBindActivatorView(vh, action);
+        }
         setEditingMode(vh, action, false);
         if (action.isFocusable()) {
             vh.itemView.setFocusable(true);
@@ -580,8 +668,7 @@
     }
 
     public void setEditingMode(ViewHolder vh, GuidedAction action, boolean editing) {
-        if (editing != vh.mInEditing) {
-            vh.mInEditing = editing;
+        if (editing != vh.isInEditing() && !isInExpandTransition()) {
             onEditingModeChange(vh, action, editing);
         }
     }
@@ -604,12 +691,15 @@
                     descriptionView.setVisibility(View.VISIBLE);
                     descriptionView.setInputType(action.getDescriptionEditInputType());
                 }
-                vh.mInEditingDescription = true;
-            } else {
-                vh.mInEditingDescription = false;
+                vh.mEditingMode = EDITING_DESCRIPTION;
+            } else if (action.isEditable()){
                 if (titleView != null) {
                     titleView.setInputType(action.getEditInputType());
                 }
+                vh.mEditingMode = EDITING_TITLE;
+            } else if (vh.mActivatorView != null) {
+                onEditActivatorView(vh, action, editing);
+                vh.mEditingMode = EDITING_ACTIVATOR_VIEW;
             }
         } else {
             if (titleView != null) {
@@ -618,18 +708,22 @@
             if (descriptionView != null) {
                 descriptionView.setText(action.getDescription());
             }
-            if (vh.mInEditingDescription) {
+            if (vh.mEditingMode == EDITING_DESCRIPTION) {
                 if (descriptionView != null) {
                     descriptionView.setVisibility(TextUtils.isEmpty(action.getDescription()) ?
                             View.GONE : View.VISIBLE);
                     descriptionView.setInputType(action.getDescriptionInputType());
                 }
-                vh.mInEditingDescription = false;
-            } else {
+            } else if (vh.mEditingMode == EDITING_TITLE) {
                 if (titleView != null) {
                     titleView.setInputType(action.getInputType());
                 }
+            } else if (vh.mEditingMode == EDITING_ACTIVATOR_VIEW) {
+                if (vh.mActivatorView != null) {
+                    onEditActivatorView(vh, action, editing);
+                }
             }
+            vh.mEditingMode = EDITING_NONE;
         }
     }
 
@@ -716,6 +810,76 @@
     }
 
     /**
+     * Performs binding activator view value to action.  Default implementation supports
+     * GuidedDatePickerAction, subclass may override to add support of other views.
+     * @param vh ViewHolder of activator view.
+     * @param action GuidedAction to bind.
+     */
+    public void onBindActivatorView(ViewHolder vh, GuidedAction action) {
+        if (action instanceof GuidedDatePickerAction) {
+            GuidedDatePickerAction dateAction = (GuidedDatePickerAction) action;
+            DatePicker dateView = (DatePicker) vh.mActivatorView;
+            dateView.setDatePickerFormat(dateAction.getDatePickerFormat());
+            Calendar c = Calendar.getInstance();
+            c.setTimeInMillis(((GuidedDatePickerAction) action).getDate());
+            dateView.updateDate(c.get(Calendar.YEAR), c.get(Calendar.MONTH),
+                    c.get(Calendar.DAY_OF_MONTH), false);
+        }
+    }
+
+    /**
+     * Performs updating GuidedAction from activator view.  Default implementation supports
+     * GuidedDatePickerAction, subclass may override to add support of other views.
+     * @param vh ViewHolder of activator view.
+     * @param action GuidedAction to update.
+     * @return True if value has been updated, false otherwise.
+     */
+    public boolean onUpdateActivatorView(ViewHolder vh, GuidedAction action) {
+        if (action instanceof GuidedDatePickerAction) {
+            GuidedDatePickerAction dateAction = (GuidedDatePickerAction) action;
+            DatePicker dateView = (DatePicker) vh.mActivatorView;
+            if (dateAction.getDate() != dateView.getDate()) {
+                dateAction.setDate(dateView.getDate());
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Sets listener for reporting view being edited.
+     * @hide
+     */
+    public void setEditListener(EditListener listener) {
+        mEditListener = listener;
+    }
+
+    void onEditActivatorView(final ViewHolder vh, final GuidedAction action,
+            boolean editing) {
+        if (editing) {
+            vh.mActivatorView.requestFocus();
+            setExpandedViewHolder(vh);
+            vh.mActivatorView.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    if (!isInExpandTransition()) {
+                        setEditingMode(vh, action, false);
+                    }
+                }
+            });
+        } else {
+            if (onUpdateActivatorView(vh, action)) {
+                if (mEditListener != null) {
+                    mEditListener.onGuidedActionEdited(action);
+                }
+            }
+            vh.itemView.requestFocus();
+            setExpandedViewHolder(null);
+            vh.mActivatorView.setOnClickListener(null);
+        }
+    }
+
+    /**
      * Sets states of chevron view, called by {@link #onBindViewHolder(ViewHolder, GuidedAction)}.
      * Subclass may override.
      *
@@ -750,7 +914,7 @@
      * hide the other items in main list.  When null, collapse the sub actions list.
      */
     public void setExpandedViewHolder(ViewHolder avh) {
-        if (mSubActionsGridView == null || isInExpandTransition()) {
+        if (isInExpandTransition()) {
             return;
         }
         if (isExpandTransitionSupported()) {
@@ -804,12 +968,15 @@
             onUpdateExpandedViewHolder(avh);
             return;
         }
+        boolean isSubActionTransition = focusAvh.getAction().hasSubActions();
         Object set = TransitionHelper.createTransitionSet(false);
+        float slideDistance = isSubActionTransition ? focusAvh.itemView.getHeight() :
+                focusAvh.itemView.getHeight() * 0.5f;
         Object slideAndFade = TransitionHelper.createFadeAndShortSlide(Gravity.TOP | Gravity.BOTTOM,
-                (float) focusAvh.itemView.getHeight());
+                slideDistance);
         Object changeFocusItemTransform = TransitionHelper.createChangeTransform();
         Object changeFocusItemBounds = TransitionHelper.createChangeBounds(false);
-        Object fadeGrid = TransitionHelper.createFadeTransition(TransitionHelper.FADE_IN |
+        Object fade = TransitionHelper.createFadeTransition(TransitionHelper.FADE_IN |
                 TransitionHelper.FADE_OUT);
         Object changeGridBounds = TransitionHelper.createChangeBounds(false);
         if (avh == null) {
@@ -817,7 +984,7 @@
             TransitionHelper.setStartDelay(changeFocusItemTransform, 100);
             TransitionHelper.setStartDelay(changeFocusItemBounds, 100);
         } else {
-            TransitionHelper.setStartDelay(fadeGrid, 100);
+            TransitionHelper.setStartDelay(fade, 100);
             TransitionHelper.setStartDelay(changeGridBounds, 100);
             TransitionHelper.setStartDelay(changeFocusItemTransform, 50);
             TransitionHelper.setStartDelay(changeFocusItemBounds, 50);
@@ -827,19 +994,25 @@
                     .getChildViewHolder(mActionsGridView.getChildAt(i));
             if (vh == focusAvh) {
                 // going to expand/collapse this one.
-                TransitionHelper.include(changeFocusItemTransform, vh.itemView);
-                TransitionHelper.include(changeFocusItemBounds, vh.itemView);
+                if (isSubActionTransition) {
+                    TransitionHelper.include(changeFocusItemTransform, vh.itemView);
+                    TransitionHelper.include(changeFocusItemBounds, vh.itemView);
+                }
             } else {
                 // going to slide this item to top / bottom.
                 TransitionHelper.include(slideAndFade, vh.itemView);
+                TransitionHelper.exclude(fade, vh.itemView, true);
             }
         }
-        TransitionHelper.include(fadeGrid, mSubActionsGridView);
         TransitionHelper.include(changeGridBounds, mSubActionsGridView);
         TransitionHelper.addTransition(set, slideAndFade);
-        TransitionHelper.addTransition(set, changeFocusItemTransform);
-        TransitionHelper.addTransition(set, changeFocusItemBounds);
-        TransitionHelper.addTransition(set, fadeGrid);
+        // note that we don't run ChangeBounds for activating view due to the rounding problem
+        // of multiple level views ChangeBounds animation causing vertical jittering.
+        if (isSubActionTransition) {
+            TransitionHelper.addTransition(set, changeFocusItemTransform);
+            TransitionHelper.addTransition(set, changeFocusItemBounds);
+        }
+        TransitionHelper.addTransition(set, fade);
         TransitionHelper.addTransition(set, changeGridBounds);
         mExpandTransition = set;
         TransitionHelper.addTransitionListener(mExpandTransition, new TransitionListener() {
@@ -917,7 +1090,7 @@
             updateChevronAndVisibility(vh);
         }
         if (mSubActionsGridView != null) {
-            if (avh != null) {
+            if (avh != null && avh.getAction().hasSubActions()) {
                 ViewGroup.MarginLayoutParams lp =
                         (ViewGroup.MarginLayoutParams) mSubActionsGridView.getLayoutParams();
                 lp.topMargin = avh.itemView.getTop();
@@ -928,7 +1101,7 @@
                 mSubActionsGridView.setSelectedPosition(0);
                 ((GuidedActionAdapter) mSubActionsGridView.getAdapter())
                         .setActions(avh.getAction().getSubActions());
-            } else {
+            } else if (mSubActionsGridView.getVisibility() == View.VISIBLE) {
                 mSubActionsGridView.setVisibility(View.INVISIBLE);
                 ViewGroup.MarginLayoutParams lp =
                         (ViewGroup.MarginLayoutParams) mSubActionsGridView.getLayoutParams();
@@ -946,9 +1119,17 @@
             if (mExpandedAction == null) {
                 vh.itemView.setVisibility(View.VISIBLE);
                 vh.itemView.setTranslationY(0);
+                if (vh.mActivatorView != null) {
+                    vh.mActivatorView.setActivated(false);
+                }
             } else if (vh.getAction() == mExpandedAction) {
                 vh.itemView.setVisibility(View.VISIBLE);
-                vh.itemView.setTranslationY(- vh.itemView.getHeight());
+                if (vh.getAction().hasSubActions()) {
+                    vh.itemView.setTranslationY(- vh.itemView.getHeight());
+                } else if (vh.mActivatorView != null) {
+                    vh.itemView.setTranslationY(0);
+                    vh.mActivatorView.setActivated(true);
+                }
             } else {
                 vh.itemView.setVisibility(View.INVISIBLE);
                 vh.itemView.setTranslationY(0);
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidedDatePickerAction.java b/v17/leanback/src/android/support/v17/leanback/widget/GuidedDatePickerAction.java
new file mode 100644
index 0000000..9daed2a
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GuidedDatePickerAction.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.content.Context;
+import android.support.v17.leanback.widget.picker.DatePicker;
+
+import java.util.Calendar;
+
+/**
+ * Subclass of GuidedAction that can choose a date.  The Action is editable by default; to make it
+ * read only, call hasEditableActivatorView(false) on the Builder.
+ */
+public class GuidedDatePickerAction extends GuidedAction {
+
+    /**
+     * Base Builder class to build GuidedDatePickerAction.  Subclass this BuilderBase when app needs
+     * to subclass GuidedDatePickerAction, implement your build() which should call
+     * {@link #applyDatePickerValues(GuidedDatePickerAction)}.  When using GuidedDatePickerAction
+     * directly, use {@link Builder}.
+     */
+    public abstract static class BuilderBase<B extends BuilderBase>
+            extends GuidedAction.BuilderBase<B> {
+
+        private String mDatePickerFormat;
+        private long mDate;
+
+        public BuilderBase(Context context) {
+            super(context);
+            Calendar c = Calendar.getInstance();
+            mDate = c.getTimeInMillis();
+            hasEditableActivatorView(true);
+        }
+
+        /**
+         * Sets format of date Picker.  When the format is not specified,
+         * a default format of current locale will be used.
+         * @param format Format of showing Date, e.g. "YMD"
+         * @return This Builder object.
+         */
+        public B datePickerFormat(String format) {
+            mDatePickerFormat = format;
+            return (B) this;
+        }
+
+        /**
+         * Sets a Date for date picker, see {@link Calendar#getTimeInMillis()}.
+         * @param date See {@link Calendar#getTimeInMillis()}.
+         * @return This Builder Object.
+         */
+        public B date(long date) {
+            mDate = date;
+            return (B) this;
+        }
+
+        /**
+         * Apply values to GuidedDatePickerAction.
+         * @param action GuidedDatePickerAction to apply values.
+         */
+        protected final void applyDatePickerValues(GuidedDatePickerAction action) {
+            super.applyValues(action);
+            action.mDatePickerFormat = mDatePickerFormat;
+            action.mDate = mDate;
+        }
+
+    }
+
+    /**
+     * Builder class to build a GuidedDatePickerAction.
+     */
+    public final static class Builder extends BuilderBase<Builder> {
+        public Builder(Context context) {
+            super(context);
+        }
+
+        /**
+         * Builds the GuidedDatePickerAction corresponding to this Builder.
+         * @return The GuidedDatePickerAction as configured through this Builder.
+         */
+        public GuidedDatePickerAction build() {
+            GuidedDatePickerAction action = new GuidedDatePickerAction();
+            applyDatePickerValues(action);
+            return action;
+        }
+    }
+
+    private String mDatePickerFormat;
+    private long mDate;
+
+    /**
+     * Returns format of date Picker or null if not specified.  When the
+     * format is not specified, a default format of current locale will be used.
+     * @return Format of showing Date, e.g. "YMD".  Returns null if using current locale's default.
+     */
+    public String getDatePickerFormat() {
+        return mDatePickerFormat;
+    }
+
+    /**
+     * Get current value of DatePicker;
+     * @return Current value of DatePicker;
+     */
+    public long getDate() {
+        return mDate;
+    }
+
+    /**
+     * Sets current value of DatePicker;
+     * @param date New value to update current value of DatePicker;
+     */
+    public void setDate(long date) {
+        mDate = date;
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/picker/DatePicker.java b/v17/leanback/src/android/support/v17/leanback/widget/picker/DatePicker.java
index 00c6f3c..209b7e6 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/picker/DatePicker.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/picker/DatePicker.java
@@ -39,8 +39,8 @@
  * @attr ref R.styleable#lbDatePicker_android_maxDate
  * @attr ref R.styleable#lbDatePicker_android_minDate
  * @attr ref R.styleable#lbDatePicker_datePickerFormat
+ * @hide
  */
-
 public class DatePicker extends Picker {
 
     static final String LOG_TAG = "DatePicker";
@@ -62,23 +62,20 @@
     Calendar mCurrentDate;
     Calendar mTempDate;
 
+    public DatePicker(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
     public DatePicker(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
 
         updateCurrentLocale();
+        setSeparator(mConstant.dateSeparator);
 
         final TypedArray attributesArray = context.obtainStyledAttributes(attrs,
                 R.styleable.lbDatePicker);
         String minDate = attributesArray.getString(R.styleable.lbDatePicker_android_minDate);
         String maxDate = attributesArray.getString(R.styleable.lbDatePicker_android_maxDate);
-        String datePickerFormat = attributesArray
-                .getString(R.styleable.lbDatePicker_datePickerFormat);
-        if (TextUtils.isEmpty(datePickerFormat)) {
-            datePickerFormat = new String(
-                    android.text.format.DateFormat.getDateFormatOrder(context));
-        }
-        setDatePickerFormat(datePickerFormat);
-
         mTempDate.clear();
         if (!TextUtils.isEmpty(minDate)) {
             if (!parseDate(minDate, mTempDate)) {
@@ -87,7 +84,7 @@
         } else {
             mTempDate.set(1900, 0, 1);
         }
-        setMinDate(mTempDate.getTimeInMillis());
+        mMinDate.setTimeInMillis(mTempDate.getTimeInMillis());
 
         mTempDate.clear();
         if (!TextUtils.isEmpty(maxDate)) {
@@ -97,8 +94,15 @@
         } else {
             mTempDate.set(2100, 0, 1);
         }
-        setMaxDate(mTempDate.getTimeInMillis());
+        mMaxDate.setTimeInMillis(mTempDate.getTimeInMillis());
 
+        String datePickerFormat = attributesArray
+                .getString(R.styleable.lbDatePicker_datePickerFormat);
+        if (TextUtils.isEmpty(datePickerFormat)) {
+            datePickerFormat = new String(
+                    android.text.format.DateFormat.getDateFormatOrder(context));
+        }
+        setDatePickerFormat(datePickerFormat);
     }
 
     private boolean parseDate(String date, Calendar outDate) {
@@ -112,10 +116,14 @@
     }
 
     /**
-     * Changes format of showing dates, e.g. 'YMD'.
+     * Changes format of showing dates.  For example "YMD".
      * @param datePickerFormat Format of showing dates.
      */
     public void setDatePickerFormat(String datePickerFormat) {
+        if (TextUtils.isEmpty(datePickerFormat)) {
+            datePickerFormat = new String(
+                    android.text.format.DateFormat.getDateFormatOrder(getContext()));
+        }
         datePickerFormat = datePickerFormat.toUpperCase();
         if (TextUtils.equals(mDatePickerFormat, datePickerFormat)) {
             return;
@@ -132,14 +140,14 @@
                 }
                 columns.add(mYearColumn = new PickerColumn());
                 mColYearIndex = i;
-                mYearColumn.setValueLabelFormat("%d");
+                mYearColumn.setEntryFormat("%d");
                 break;
             case 'M':
                 if (mMonthColumn != null) {
                     throw new IllegalArgumentException("datePicker format error");
                 }
                 columns.add(mMonthColumn = new PickerColumn());
-                mMonthColumn.setValueStaticLabels(mConstant.months);
+                mMonthColumn.setEntries(mConstant.months);
                 mColMonthIndex = i;
                 break;
             case 'D':
@@ -147,7 +155,7 @@
                     throw new IllegalArgumentException("datePicker format error");
                 }
                 columns.add(mDayColumn = new PickerColumn());
-                mDayColumn.setValueLabelFormat("%02d");
+                mDayColumn.setEntryFormat("%02d");
                 mColDayIndex = i;
                 break;
             default:
@@ -159,20 +167,14 @@
     }
 
     /**
-     * Get format of showing dates, e.g. 'YMD'.  Default value is from
-     * {@link android.text.format.DateFormat#getDateFormatOrder}.
+     * Get format of showing dates.  For example "YMD".  Default value is from
+     * {@link android.text.format.DateFormat#getDateFormatOrder(Context)}.
      * @return Format of showing dates.
      */
     public String getDatePickerFormat() {
         return mDatePickerFormat;
     }
 
-    @Override
-    protected String getSeparator() {
-        return mConstant.dateSeparator;
-    }
-
-
     private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) {
         if (oldCalendar == null) {
             return Calendar.getInstance(locale);
@@ -192,13 +194,13 @@
         mCurrentDate = getCalendarForLocale(mCurrentDate, mConstant.locale);
 
         if (mMonthColumn != null) {
-            mMonthColumn.setValueStaticLabels(mConstant.months);
-            updateAdapter(mColMonthIndex);
+            mMonthColumn.setEntries(mConstant.months);
+            setColumnAt(mColMonthIndex, mMonthColumn);
         }
     }
 
     @Override
-    public void onColumnValueChange(int column, int newVal) {
+    public final void onColumnValueChanged(int column, int newVal) {
         mTempDate.setTimeInMillis(mCurrentDate.getTimeInMillis());
         // take care of wrapping of days and months to update greater fields
         int oldVal = getColumnAt(column).getCurrentValue();
@@ -248,10 +250,8 @@
      *
      * @return The minimal supported date.
      */
-    public Calendar getMinDate() {
-        final Calendar minDate = Calendar.getInstance();
-        minDate.setTimeInMillis(mMinDate.getTimeInMillis());
-        return minDate;
+    public long getMinDate() {
+        return mMinDate.getTimeInMillis();
     }
 
     /**
@@ -284,10 +284,18 @@
      *
      * @return The maximal supported date.
      */
-    public Calendar getMaxDate() {
-        final Calendar maxDate = Calendar.getInstance();
-        maxDate.setTimeInMillis(mMaxDate.getTimeInMillis());
-        return maxDate;
+    public long getMaxDate() {
+        return mMaxDate.getTimeInMillis();
+    }
+
+    /**
+     * Gets current date value in milliseconds since January 1, 1970 00:00:00 in
+     * {@link TimeZone#getDefault()} time zone.
+     *
+     * @return Current date values.
+     */
+    public long getDate() {
+        return mCurrentDate.getTimeInMillis();
     }
 
     private void setDate(int year, int month, int dayOfMonth) {
@@ -321,72 +329,88 @@
                 || mCurrentDate.get(Calendar.DAY_OF_MONTH) != month);
     }
 
+    private static boolean updateMin(PickerColumn column, int value) {
+        if (value != column.getMinValue()) {
+            column.setMinValue(value);
+            return true;
+        }
+        return false;
+    }
+
+    private static boolean updateMax(PickerColumn column, int value) {
+        if (value != column.getMaxValue()) {
+            column.setMaxValue(value);
+            return true;
+        }
+        return false;
+    }
+
     private void updateSpinners(boolean animation) {
         // set the spinner ranges respecting the min and max dates
         boolean dayRangeChanged = false;
         boolean monthRangeChanged = false;
         if (mCurrentDate.equals(mMinDate)) {
             if (mDayColumn != null) {
-                dayRangeChanged |= mDayColumn.setMinValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
-                dayRangeChanged |= mDayColumn
-                        .setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
+                dayRangeChanged |= updateMin(mDayColumn, mCurrentDate.get(Calendar.DAY_OF_MONTH));
+                dayRangeChanged |=
+                        updateMax(mDayColumn, mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
             }
             if (mMonthColumn != null) {
-                monthRangeChanged |= mMonthColumn.setMinValue(mCurrentDate.get(Calendar.MONTH));
+                monthRangeChanged |= updateMin(mMonthColumn, mCurrentDate.get(Calendar.MONTH));
                 monthRangeChanged |=
-                        mMonthColumn.setMaxValue(mCurrentDate.getActualMaximum(Calendar.MONTH));
+                        updateMax(mMonthColumn, mCurrentDate.getActualMaximum(Calendar.MONTH));
             }
         } else if (mCurrentDate.equals(mMaxDate)) {
             if (mDayColumn != null) {
-                dayRangeChanged |= mDayColumn
-                        .setMinValue(mCurrentDate.getActualMinimum(Calendar.DAY_OF_MONTH));
-                dayRangeChanged |= mDayColumn.setMaxValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
+                dayRangeChanged |=
+                        updateMin(mDayColumn, mCurrentDate.getActualMinimum(Calendar.DAY_OF_MONTH));
+                dayRangeChanged |= updateMax(mDayColumn, mCurrentDate.get(Calendar.DAY_OF_MONTH));
             }
             if (mMonthColumn != null) {
                 monthRangeChanged |=
-                        mMonthColumn.setMinValue(mCurrentDate.getActualMinimum(Calendar.MONTH));
-                monthRangeChanged |= mMonthColumn.setMaxValue(mCurrentDate.get(Calendar.MONTH));
+                        updateMin(mMonthColumn, mCurrentDate.getActualMinimum(Calendar.MONTH));
+                monthRangeChanged |= updateMax(mMonthColumn, mCurrentDate.get(Calendar.MONTH));
             }
         } else {
             if (mDayColumn != null) {
-                dayRangeChanged |= mDayColumn
-                        .setMinValue(mCurrentDate.getActualMinimum(Calendar.DAY_OF_MONTH));
-                dayRangeChanged |= mDayColumn
-                        .setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
+                dayRangeChanged |=
+                        updateMin(mDayColumn, mCurrentDate.getActualMinimum(Calendar.DAY_OF_MONTH));
+                dayRangeChanged |=
+                        updateMax(mDayColumn, mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
             }
             if (mMonthColumn != null) {
                 monthRangeChanged |=
-                        mMonthColumn.setMinValue(mCurrentDate.getActualMinimum(Calendar.MONTH));
+                        updateMin(mMonthColumn, mCurrentDate.getActualMinimum(Calendar.MONTH));
                 monthRangeChanged |=
-                        mMonthColumn.setMaxValue(mCurrentDate.getActualMaximum(Calendar.MONTH));
+                        updateMax(mMonthColumn, mCurrentDate.getActualMaximum(Calendar.MONTH));
             }
         }
 
         // year spinner range does not change based on the current date
         boolean yearRangeChanged = false;
         if (mYearColumn != null) {
-            yearRangeChanged |= mYearColumn.setMinValue(mMinDate.get(Calendar.YEAR));
-            yearRangeChanged |= mYearColumn.setMaxValue(mMaxDate.get(Calendar.YEAR));
+            yearRangeChanged |= updateMin(mYearColumn, mMinDate.get(Calendar.YEAR));
+            yearRangeChanged |= updateMax(mYearColumn, mMaxDate.get(Calendar.YEAR));
         }
 
         if (dayRangeChanged) {
-            updateAdapter(mColDayIndex);
+            setColumnAt(mColDayIndex, mDayColumn);
         }
         if (monthRangeChanged) {
-            updateAdapter(mColMonthIndex);
+            setColumnAt(mColMonthIndex, mMonthColumn);
         }
         if (yearRangeChanged) {
-            updateAdapter(mColYearIndex);
+            setColumnAt(mColYearIndex, mYearColumn);
         }
         // set the spinner values
         if (mYearColumn != null) {
-            updateValue(mColYearIndex, mCurrentDate.get(Calendar.YEAR), animation);
+            setColumnValue(mColYearIndex, mCurrentDate.get(Calendar.YEAR), animation);
         }
         if (mMonthColumn != null) {
-            updateValue(mColMonthIndex, mCurrentDate.get(Calendar.MONTH), animation);
+            setColumnValue(mColMonthIndex, mCurrentDate.get(Calendar.MONTH), animation);
         }
         if (mDayColumn != null) {
-            updateValue(mColDayIndex, mCurrentDate.get(Calendar.DAY_OF_MONTH), animation);
+            setColumnValue(mColDayIndex, mCurrentDate.get(Calendar.DAY_OF_MONTH), animation);
         }
 
     }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/picker/Picker.java b/v17/leanback/src/android/support/v17/leanback/widget/picker/Picker.java
index 1157a11..8e5bcdc 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/picker/Picker.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/picker/Picker.java
@@ -36,28 +36,19 @@
 
 /**
  * Picker is a widget showing multiple customized {@link PickerColumn}s. The PickerColumns are
- * initialized in {@link #setColumns(ArrayList)}. Call {@link #updateAdapter(int)} if the column
- * value range or labels change. Call {@link #updateValue(int, int, boolean)} to update the current
- * value of PickerColumn.
+ * initialized in {@link #setColumns(List)}. Call {@link #setColumnAt(int, PickerColumn)} if the
+ * column value range or labels change. Call {@link #setColumnValue(int, int, boolean)} to update
+ * the current value of PickerColumn.
  * <p>
  * Picker has two states and will change height:
- * <li>{@link #isExpanded()} is true: Picker shows typically three items vertically (see
- * {@link #getVisiblePickerItemsInExpand()}}. Columns other than {@link #getActiveColumn()} still
+ * <li>{@link #isActivated()} is true: Picker shows typically three items vertically (see
+ * {@link #getActivatedVisibleItemCount()}}. Columns other than {@link #getSelectedColumn()} still
  * shows one item if the Picker is focused. On a touch screen device, the Picker will not get focus
  * so it always show three items on all columns. On a non-touch device (a TV), the Picker will show
  * three items only on currently activated column. If the Picker has focus, it will intercept DPAD
  * directions and select activated column.
- * <li>{@link #isExpanded()} is false: Picker shows one item vertically (see
- * {@link #getVisiblePickerItems()}) on all columns. The size of Picker shrinks.
- * <li>The expand mode will be toggled if the Picker has focus and {@link #isToggleExpandOnClick()}
- * is true. Summarize Typically use cases:
- * <li>On a touch screen based device, the Picker focusableInTouchMode=false. It won't get focus, it
- * wont toggle expand mode on click or touch, should call {@link #setExpanded(boolean)} with true,
- * so that user always sees three items on all columns.
- * <li>On a TV: the Picker focusable=true. It will get focus and toggle into expand mode when user
- * clicks on it, toggle can be disabled by {@link #setToggleExpandOnClick(boolean)} with false. Only
- * the activated column shows multiple items and the activated column is selected by DPAD left or
- * right.
+ * <li>{@link #isActivated()} is false: Picker shows one item vertically (see
+ * {@link #getVisibleItemCount()}) on all columns. The size of Picker shrinks.
  */
 public class Picker extends FrameLayout {
 
@@ -65,7 +56,6 @@
         public void onValueChanged(Picker picker, int column);
     }
 
-    private String mSeparator;
     private ViewGroup mRootView;
     private ViewGroup mPickerView;
     private List<VerticalGridView> mColumnViews = new ArrayList<VerticalGridView>();
@@ -79,62 +69,57 @@
     private Interpolator mDecelerateInterpolator;
     private Interpolator mAccelerateInterpolator;
     private ArrayList<PickerValueListener> mListeners;
-    private boolean mExpanded;
-    private float mVisibleItemsInExpand = 3;
+    private float mVisibleItemsActivated = 3;
     private float mVisibleItems = 1;
-    private int mActivatedColumn = 0;
-    private boolean mToggleExpandOnClick = true;
+    private int mSelectedColumn = 0;
+
+    private CharSequence mSeparator;
+    private int mPickerItemLayoutId = R.layout.lb_picker_item;
+    private int mPickerItemTextViewId = 0;
 
     /**
-     * Classes extending {@link Picker} can choose to override this method to
-     * supply the separator string
+     * Gets separator string between columns.
      */
-    protected String getSeparator() {
+    public final CharSequence getSeparator() {
         return mSeparator;
     }
 
     /**
-     * Classes extending {@link Picker} can choose to override this method to
-     * supply the {@link Picker}'s root layout id
+     * Sets separator String between Picker columns.
+     * @param seperator Separator String between Picker columns.
      */
-    protected int getRootLayoutId() {
-        return R.layout.lb_picker;
-    }
-
-    /**
-     * Classes extending {@link Picker} can choose to override this method to
-     * supply the {@link Picker}'s id from within the layout provided by
-     * {@link Picker#getRootLayoutId()}
-     */
-    protected int getPickerId() {
-        return R.id.picker;
-    }
-
-    /**
-     * Classes extending {@link Picker} can choose to override this method to
-     * supply the {@link Picker}'s separator's layout id
-     */
-    protected int getPickerSeparatorLayoutId() {
-        return R.layout.lb_picker_separator;
+    public final void setSeparator(CharSequence seperator) {
+        mSeparator = seperator;
     }
 
     /**
      * Classes extending {@link Picker} can choose to override this method to
      * supply the {@link Picker}'s item's layout id
      */
-    protected int getPickerItemLayoutId() {
-        return R.layout.lb_picker_item;
+    public final int getPickerItemLayoutId() {
+        return mPickerItemLayoutId;
     }
 
     /**
-     * Classes extending {@link Picker} can choose to override this method to
-     * supply the {@link Picker}'s item's {@link TextView}'s id from within the
+     * Returns the {@link Picker}'s item's {@link TextView}'s id from within the
      * layout provided by {@link Picker#getPickerItemLayoutId()} or 0 if the
      * layout provided by {@link Picker#getPickerItemLayoutId()} is a {link
      * TextView}.
      */
-    protected int getPickerItemTextViewId() {
-        return 0;
+    public final int getPickerItemTextViewId() {
+        return mPickerItemTextViewId;
+    }
+
+    /**
+     * Sets the {@link Picker}'s item's {@link TextView}'s id from within the
+     * layout provided by {@link Picker#getPickerItemLayoutId()} or 0 if the
+     * layout provided by {@link Picker#getPickerItemLayoutId()} is a {link
+     * TextView}.
+     * @param textViewId View id of TextView inside a Picker item, or 0 if the Picker item is a
+     *                   TextView.
+     */
+    public final void setPickerItemTextViewId(int textViewId) {
+        mPickerItemTextViewId = textViewId;
     }
 
     /**
@@ -164,15 +149,15 @@
         mAccelerateInterpolator = new AccelerateInterpolator(2.5F);
 
         LayoutInflater inflater = LayoutInflater.from(getContext());
-        mRootView = (ViewGroup) inflater.inflate(getRootLayoutId(), this, true);
-        mPickerView = (ViewGroup) mRootView.findViewById(getPickerId());
+        mRootView = (ViewGroup) inflater.inflate(R.layout.lb_picker, this, true);
+        mPickerView = (ViewGroup) mRootView.findViewById(R.id.picker);
 
     }
 
     /**
      * Get nth PickerColumn.
      * @param colIndex  Index of PickerColumn.
-     * @return PickerColumn at colIndex or null if {@link #setColumns(ArrayList)} is not called yet.
+     * @return PickerColumn at colIndex or null if {@link #setColumns(List)} is not called yet.
      */
     public PickerColumn getColumnAt(int colIndex) {
         if (mColumns == null) {
@@ -183,7 +168,7 @@
 
     /**
      * Get number of PickerColumns.
-     * @return Number of PickerColumns or 0 if {@link #setColumns(ArrayList)} is not called yet.
+     * @return Number of PickerColumns or 0 if {@link #setColumns(List)} is not called yet.
      */
     public int getColumnsCount() {
         if (mColumns == null) {
@@ -196,12 +181,12 @@
      * Set columns and create Views.
      * @param columns PickerColumns to be shown in the Picker.
      */
-    public void setColumns(ArrayList<PickerColumn> columns) {
+    public void setColumns(List<PickerColumn> columns) {
         mColumnViews.clear();
         mPickerView.removeAllViews();
         mColumns = new ArrayList<PickerColumn>(columns);
-        if (mActivatedColumn > mColumns.size() - 1) {
-            mActivatedColumn = mColumns.size() - 1;
+        if (mSelectedColumn > mColumns.size() - 1) {
+            mSelectedColumn = mColumns.size() - 1;
         }
         LayoutInflater inflater = LayoutInflater.from(getContext());
         int totalCol = getColumnsCount();
@@ -225,7 +210,7 @@
             // add a separator if not the last element
             if (i != totalCol - 1 && getSeparator() != null) {
                 TextView separator = (TextView) inflater.inflate(
-                        getPickerSeparatorLayoutId(), mPickerView, false);
+                        R.layout.lb_picker_separator, mPickerView, false);
                 separator.setText(getSeparator());
                 mPickerView.addView(separator);
             }
@@ -240,8 +225,10 @@
      * When column labels change or column range changes, call this function to re-populate the
      * selection list.
      * @param columnIndex Index of column to update.
+     * @param column New column to update.
      */
-    public void updateAdapter(int columnIndex) {
+    public void setColumnAt(int columnIndex, PickerColumn column) {
+        mColumns.set(columnIndex, column);
         VerticalGridView columnView = mColumnViews.get(columnIndex);
         PickerScrollArrayAdapter adapter = (PickerScrollArrayAdapter) columnView.getAdapter();
         if (adapter != null && !columnView.isComputingLayout()) {
@@ -255,8 +242,10 @@
      * @param value New value of the column.
      * @param runAnimation True to scroll to the value or false otherwise.
      */
-    public void updateValue(int columnIndex, int value, boolean runAnimation) {
-        if (mColumns.get(columnIndex).setCurrentValue(value)) {
+    public void setColumnValue(int columnIndex, int value, boolean runAnimation) {
+        PickerColumn column = mColumns.get(columnIndex);
+        if (column.getCurrentValue() != value) {
+            column.setCurrentValue(value);
             notifyValueChanged(columnIndex);
             VerticalGridView columnView = mColumnViews.get(columnIndex);
             if (columnView != null) {
@@ -278,14 +267,22 @@
         }
     }
 
-    public void addPickerValueListener(PickerValueListener listener) {
+    /**
+     * Register a callback to be invoked when the picker's value has changed.
+     * @param listener The callback to ad
+     */
+    public void addOnValueChangedListener(PickerValueListener listener) {
         if (mListeners == null) {
             mListeners = new ArrayList<Picker.PickerValueListener>();
         }
         mListeners.add(listener);
     }
 
-    public void removePickerValueListener(PickerValueListener listener) {
+    /**
+     * Remove a previously installed value changed callback
+     * @param listener The callback to remove.
+     */
+    public void removeOnValueChangedListener(PickerValueListener listener) {
         if (mListeners != null) {
             mListeners.remove(listener);
         }
@@ -307,7 +304,7 @@
 
     private void setOrAnimateAlpha(View view, boolean selected, int colIndex,
             boolean animate) {
-        boolean columnShownAsActivated = colIndex == mActivatedColumn || !isFocused();
+        boolean columnShownAsActivated = colIndex == mSelectedColumn || !isFocused();
         if (selected) {
             // set alpha for main item (selected) in the column
             if (columnShownAsActivated) {
@@ -344,15 +341,17 @@
 
     /**
      * Classes extending {@link Picker} can override this function to supply the
-     * behavior when a list has been scrolled.  Subclass may call {@link #updateValue(int, int,
-     * boolean)} and or {@link #updateAdapter(int)}.  Subclass should not directly call
+     * behavior when a list has been scrolled.  Subclass may call {@link #setColumnValue(int, int,
+     * boolean)} and or {@link #setColumnAt(int,PickerColumn)}.  Subclass should not directly call
      * {@link PickerColumn#setCurrentValue(int)} which does not update internal state or notify
      * listeners.
      * @param columnIndex index of which column was changed.
      * @param newValue A new value desired to be set on the column.
      */
-    public void onColumnValueChange(int columnIndex, int newValue) {
-        if (mColumns.get(columnIndex).setCurrentValue(newValue)) {
+    public void onColumnValueChanged(int columnIndex, int newValue) {
+        PickerColumn column = mColumns.get(columnIndex);
+        if (column.getCurrentValue() != newValue) {
+            column.setCurrentValue(newValue);
             notifyValueChanged(columnIndex);
         }
     }
@@ -402,7 +401,7 @@
 
         public void onBindViewHolder(ViewHolder holder, int position) {
             if (holder.textView != null && mData != null) {
-                holder.textView.setText(mData.getValueLabelAt(mData.getMinValue() + position));
+                holder.textView.setText(mData.getEntryAt(mData.getMinValue() + position));
             }
             setOrAnimateAlpha(holder.itemView,
                     (mColumnViews.get(mColIndex).getSelectedPosition() == position),
@@ -415,7 +414,7 @@
         }
 
         public int getItemCount() {
-            return mData == null ? 0 : mData.getItemsCount();
+            return mData == null ? 0 : mData.getItemCount();
         }
     }
 
@@ -432,7 +431,7 @@
             updateColumnAlpha(colIndex, true);
             if (child != null) {
                 int newValue = mColumns.get(colIndex).getMinValue() + position;
-                onColumnValueChange(colIndex, newValue);
+                onColumnValueChanged(colIndex, newValue);
             }
         }
 
@@ -440,7 +439,7 @@
 
     @Override
     public boolean dispatchKeyEvent(android.view.KeyEvent event) {
-        if (isExpanded()) {
+        if (isActivated()) {
             final int keyCode = event.getKeyCode();
             switch (keyCode) {
             case KeyEvent.KEYCODE_DPAD_LEFT:
@@ -449,20 +448,20 @@
                     if (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL?
                             keyCode == KeyEvent.KEYCODE_DPAD_LEFT :
                             keyCode == KeyEvent.KEYCODE_DPAD_RIGHT ) {
-                        if (mActivatedColumn < getColumnsCount() - 1) {
-                            setActiveColumn(mActivatedColumn + 1);
+                        if (mSelectedColumn < getColumnsCount() - 1) {
+                            setSelectedColumn(mSelectedColumn + 1);
                         }
                     } else {
-                        if (mActivatedColumn > 0) {
-                            setActiveColumn(mActivatedColumn - 1);
+                        if (mSelectedColumn > 0) {
+                            setSelectedColumn(mSelectedColumn - 1);
                         }
                     }
                 }
                 break;
             case KeyEvent.KEYCODE_DPAD_UP:
             case KeyEvent.KEYCODE_DPAD_DOWN:
-                if (event.getAction() == KeyEvent.ACTION_DOWN && mActivatedColumn >= 0) {
-                    VerticalGridView gridView = mColumnViews.get(mActivatedColumn);
+                if (event.getAction() == KeyEvent.ACTION_DOWN && mSelectedColumn >= 0) {
+                    VerticalGridView gridView = mColumnViews.get(mSelectedColumn);
                     if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
                         int newPosition = gridView.getSelectedPosition() - 1;
                         if (newPosition >= 0) {
@@ -500,90 +499,82 @@
 
     private void updateColumnSize(VerticalGridView columnView) {
         ViewGroup.LayoutParams lp = columnView.getLayoutParams();
-        lp.height = (int) (getPickerItemHeightPixels() * (isExpanded() ?
-                getVisiblePickerItemsInExpand() : getVisiblePickerItems()));
+        lp.height = (int) (getPickerItemHeightPixels() * (isActivated() ?
+                getActivatedVisibleItemCount() : getVisibleItemCount()));
         columnView.setLayoutParams(lp);
     }
 
     /**
-     * Returns number of visible items showing in a column when it's expanded, it's 3 by default.
-     * @return Number of visible items showing in a column when it's expanded.
+     * Returns number of visible items showing in a column when it's activated.  The default value
+     * is 3.
+     * @return Number of visible items showing in a column when it's activated.
      */
-    public float getVisiblePickerItemsInExpand() {
-        return mVisibleItemsInExpand;
+    public float getActivatedVisibleItemCount() {
+        return mVisibleItemsActivated;
     }
 
     /**
-     * Change number of visible items showing in a column when it's expanded.
-     * @param visiblePickerItems Number of visible items showing in a column when it's expanded.
+     * Changes number of visible items showing in a column when it's activated.  The default value
+     * is 3.
+     * @param visiblePickerItems Number of visible items showing in a column when it's activated.
      */
-    public void setVisiblePickerItemsInExpand(float visiblePickerItems) {
+    public void setActivatedVisibleItemCount(float visiblePickerItems) {
         if (visiblePickerItems <= 0) {
             throw new IllegalArgumentException();
         }
-        if (mVisibleItemsInExpand != visiblePickerItems) {
-            mVisibleItemsInExpand = visiblePickerItems;
-            if (isExpanded()) {
+        if (mVisibleItemsActivated != visiblePickerItems) {
+            mVisibleItemsActivated = visiblePickerItems;
+            if (isActivated()) {
                 updateColumnSize();
             }
         }
     }
 
     /**
-     * Returns number of visible items showing in a column when it's not expanded, it's 1 by
-     * default.
-     * @return Number of visible items showing in a column when it's not expanded.
+     * Returns number of visible items showing in a column when it's not activated.  The default
+     * value is 1.
+     * @return Number of visible items showing in a column when it's not activated.
      */
-    public float getVisiblePickerItems() {
+    public float getVisibleItemCount() {
         return 1;
     }
 
     /**
-     * Change number of visible items showing in a column when it's not expanded, it's 1 by default.
-     * @param pickerItems Number of visible items showing in a column when it's not expanded.
+     * Changes number of visible items showing in a column when it's not activated.  The default
+     * value is 1.
+     * @param pickerItems Number of visible items showing in a column when it's not activated.
      */
-    public void setVisiblePickerItems(float pickerItems) {
+    public void setVisibleItemCount(float pickerItems) {
         if (pickerItems <= 0) {
             throw new IllegalArgumentException();
         }
         if (mVisibleItems != pickerItems) {
             mVisibleItems = pickerItems;
-            if (!isExpanded()) {
+            if (!isActivated()) {
                 updateColumnSize();
             }
         }
     }
 
-    /**
-     * Change expanded state of Picker, the height LayoutParams will be changed.
-     * @see #getVisiblePickerItemsInExpand()
-     * @see #getVisiblePickerItems()
-     * @param expanded New expanded state of Picker.
-     */
-    public void setExpanded(boolean expanded) {
-        if (mExpanded != expanded) {
-            mExpanded = expanded;
+    @Override
+    public void setActivated(boolean activated) {
+        if (activated != isActivated()) {
+            super.setActivated(activated);
             updateColumnSize();
+        } else {
+            super.setActivated(activated);
         }
     }
 
     /**
-     * Returns true if the Picker is currently expanded, false otherwise.
-     * @return True if the Picker is currently expanded, false otherwise.
-     */
-    public boolean isExpanded() {
-        return mExpanded;
-    }
-
-    /**
-     * Change current activated column.  Shows multiple items on activate column if Picker has
-     * focus. Show multiple items on all column if Picker has no focus (e.g. a Touchscreen
+     * Change current selected column.  Picker shows multiple items on selected column if Picker has
+     * focus.  Picker shows multiple items on all column if Picker has no focus (e.g. a Touchscreen
      * screen).
      * @param columnIndex Index of column to activate.
      */
-    public void setActiveColumn(int columnIndex) {
-        if (mActivatedColumn != columnIndex) {
-            mActivatedColumn = columnIndex;
+    public void setSelectedColumn(int columnIndex) {
+        if (mSelectedColumn != columnIndex) {
+            mSelectedColumn = columnIndex;
             for (int i = 0; i < mColumnViews.size(); i++) {
                 updateColumnAlpha(i, true);
             }
@@ -594,35 +585,8 @@
      * Get current activated column index.
      * @return Current activated column index.
      */
-    public int getActiveColumn() {
-        return mActivatedColumn;
-    }
-
-    /**
-     * Enable or disable toggle on click when Picker has focus.
-     * @param toggleExpandOnClick True to enable toggle on click when Picker has focus, false
-     * otherwise.
-     */
-    public void setToggleExpandOnClick(boolean toggleExpandOnClick) {
-        mToggleExpandOnClick = toggleExpandOnClick;
-    }
-
-    /**
-     * Returns true if toggle on click is enabled when Picker has focus, false otherwise.
-     * @return True if toggle on click is enabled when Picker has focus, false otherwise.
-     */
-    public boolean isToggleExpandOnClick() {
-        return mToggleExpandOnClick;
-    }
-
-    @Override
-    public boolean performClick() {
-        if (isFocused() && isToggleExpandOnClick()) {
-            setExpanded(!isExpanded());
-            super.performClick();
-            return true;
-        }
-        return super.performClick();
+    public int getSelectedColumn() {
+        return mSelectedColumn;
     }
 
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/picker/PickerColumn.java b/v17/leanback/src/android/support/v17/leanback/widget/picker/PickerColumn.java
index c4c0c57..f2a2c5a 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/picker/PickerColumn.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/picker/PickerColumn.java
@@ -14,54 +14,40 @@
 
 package android.support.v17.leanback.widget.picker;
 
-import android.os.Parcel;
-import android.os.Parcelable;
-
 /**
  * Picker column class used by {@link Picker}, defines a contiguous value ranges and associated
  * labels.  A PickerColumn has a minValue and maxValue to choose between.  The Picker column has
  * a current value.
- * The labels can be dynamically generated from value by {@link #setValueLabelFormat(String)} or
- * a list of static labels set by {@link #setValueStaticLabels(String[])}.
+ * The labels can be dynamically generated from value by {@link #setEntryFormat(String)} or
+ * a list of static labels set by {@link #setEntries(CharSequence[])}.
  */
-public class PickerColumn implements Parcelable {
+public class PickerColumn {
 
     private int mCurrentValue;
     private int mMinValue;
     private int mMaxValue;
-    private String[] mStaticLabels;
-    private String mValueFormat;
+    private CharSequence[] mStaticEntrys;
+    private String mEntryFormat;
 
     public PickerColumn() {
     }
 
-    public PickerColumn(Parcel source) {
-        mValueFormat = source.readString();
-        int count = source.readInt();
-        if (count > 0) {
-            mStaticLabels = new String[count];
-            source.readStringArray(mStaticLabels);
-        }
-        mCurrentValue = source.readInt();
-        mMinValue = source.readInt();
-        mMaxValue = source.readInt();
-    }
-
     /**
-     * Set string format to display label for value, e.g. "%02d".  The string format is only
-     * used when {@link #setValueStaticLabels(String[])} is not called.
-     * @param valueFormat String format to display label for value.
+     * Set string format to display label for value. For example "%02d".
+     * {@link #setEntries(CharSequence[])} overrides the format.
+     *
+     * @param valueFormat String format to display label for value between minValue and maxValue.
      */
-    public void setValueLabelFormat(String valueFormat) {
-        mValueFormat = valueFormat;
+    public void setEntryFormat(String valueFormat) {
+        mEntryFormat = valueFormat;
     }
 
     /**
-     * Return string format to display label for value, e.g. "%02d".
+     * Return string format to display label for value.  For example "%02d".
      * @return String format to display label for value.
      */
-    public String getValueLabelFormat() {
-        return mValueFormat;
+    public String getEntryFormat() {
+        return mEntryFormat;
     }
 
     /**
@@ -69,21 +55,22 @@
      * labels[labels.length - 1].
      * @param labels Static labels for each value between minValue and maxValue.
      */
-    public void setValueStaticLabels(String[] labels) {
-        mStaticLabels = labels;
+    public void setEntries(CharSequence[] labels) {
+        mStaticEntrys = labels;
     }
 
     /**
-     * Get a label for value.  The label can be static ({@link #setValueStaticLabels(String[])} or
-     * dynamically generated (@link {@link #setValueLabelFormat(String)}.
+     * Get a label for value. The label can be static ({@link #setEntries(CharSequence[])}
+     * or dynamically generated (@link {@link #setEntryFormat(String)}.
+     * 
      * @param value Value between minValue and maxValue.
      * @return Label for the value.
      */
-    public String getValueLabelAt(int value) {
-        if (mStaticLabels == null) {
-            return String.format(mValueFormat, value);
+    public CharSequence getEntryAt(int value) {
+        if (mStaticEntrys == null) {
+            return String.format(mEntryFormat, value);
         }
-        return mStaticLabels[value];
+        return mStaticEntrys[value];
     }
 
     /**
@@ -96,97 +83,49 @@
 
     /**
      * Sets current value of the Column.
-     * @return True if current value has changed.
      */
-    public boolean setCurrentValue(int value) {
-        if (mCurrentValue != value) {
-            mCurrentValue = value;
-            return true;
-        }
-        return false;
+    public void setCurrentValue(int value) {
+        mCurrentValue = value;
     }
 
     /**
-     * Get total items count between minValue(inclusive) and maxValue (inclusive).
-     * @return Total items count between minValue(inclusive) and maxValue (inclusive).
+     * Get total items count between minValue and maxValue.
+     * @return Total items count between minValue and maxValue.
      */
-    public int getItemsCount() {
+    public int getItemCount() {
         return mMaxValue - mMinValue + 1;
     }
 
     /**
-     * Returns minimal value (inclusive) of the Column.
-     * @return Minimal value (inclusive) of the Column.
+     * Returns minimal value of the Column.
+     * @return Minimal value of the Column.
      */
     public int getMinValue() {
         return mMinValue;
     }
 
     /**
-     * Returns maximum value (inclusive) of the Column.
-     * @return Maximum value (inclusive) of the Column.
+     * Returns maximum value of the Column.
+     * @return Maximum value of the Column.
      */
     public int getMaxValue() {
         return mMaxValue;
     }
 
     /**
-     * Sets minimal value (inclusive) of the Column.
+     * Sets minimal value of the Column.
      * @param minValue New minimal value to set.
-     * @return True if minimal value changes.
      */
-    public boolean setMinValue(int minValue) {
-        if (minValue != mMinValue) {
-            mMinValue = minValue;
-            return true;
-        }
-        return false;
+    public void setMinValue(int minValue) {
+        mMinValue = minValue;
     }
 
     /**
-     * Sets maximum value (inclusive) of the Column.
+     * Sets maximum value of the Column.
      * @param maxValue New maximum value to set.
-     * @return True if maximum value changes.
      */
-    public boolean setMaxValue(int maxValue) {
-        if (maxValue != mMaxValue) {
-            mMaxValue = maxValue;
-            return true;
-        }
-        return false;
-    }
-
-    public static Parcelable.Creator<PickerColumn>
-            CREATOR = new Parcelable.Creator<PickerColumn>() {
-
-                @Override
-                public PickerColumn createFromParcel(Parcel source) {
-                    return new PickerColumn(source);
-                }
-
-                @Override
-                public PickerColumn[] newArray(int size) {
-                    return new PickerColumn[size];
-                }
-            };
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeString(mValueFormat);
-        if (mStaticLabels != null) {
-            dest.writeInt(mStaticLabels.length);
-            dest.writeStringArray(mStaticLabels);
-        } else {
-            dest.writeInt(0);
-        }
-        dest.writeInt(mCurrentValue);
-        dest.writeInt(mMinValue);
-        dest.writeInt(mMaxValue);
+    public void setMaxValue(int maxValue) {
+        mMaxValue = maxValue;
     }
 
 }
diff --git a/v17/preference-leanback/build.gradle b/v17/preference-leanback/build.gradle
index 0ddcdd2..c72f23c 100644
--- a/v17/preference-leanback/build.gradle
+++ b/v17/preference-leanback/build.gradle
@@ -16,7 +16,7 @@
 
 
 
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
 
 archivesBaseName = 'preference-leanback-v17'
 
@@ -30,7 +30,7 @@
 }
 
 android {
-    compileSdkVersion 'current'
+    compileSdkVersion project.ext.currentSdk
 
     sourceSets {
         main.manifest.srcFile 'AndroidManifest.xml'
diff --git a/v4/api/current.txt b/v4/api/current.txt
index c9a72b1..139300c 100644
--- a/v4/api/current.txt
+++ b/v4/api/current.txt
@@ -926,7 +926,7 @@
     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 static java.io.File getNoBackupFilesDir(android.content.Context);
+    method public final java.io.File getNoBackupFilesDir(android.content.Context);
     method public static java.io.File[] getObbDirs(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);
@@ -1106,8 +1106,8 @@
     method public static int XYZToColor(double, double, double);
     method public static void XYZToLAB(double, double, double, double[]);
     method public static int blendARGB(int, int, float);
-    method public static void blendHSL(float[], float[], float[], float);
-    method public static void blendLAB(double[], double[], double[], double);
+    method public static void blendHSL(float[], float[], float, float[]);
+    method public static void blendLAB(double[], double[], double, double[]);
     method public static double calculateContrast(int, int);
     method public static double calculateLuminance(int);
     method public static int calculateMinimumAlpha(int, int, float);
diff --git a/v4/api21/android/support/v4/graphics/drawable/DrawableCompatLollipop.java b/v4/api21/android/support/v4/graphics/drawable/DrawableCompatLollipop.java
index 3d0d9e6..b8ab03a 100644
--- a/v4/api21/android/support/v4/graphics/drawable/DrawableCompatLollipop.java
+++ b/v4/api21/android/support/v4/graphics/drawable/DrawableCompatLollipop.java
@@ -19,9 +19,6 @@
 import android.content.res.ColorStateList;
 import android.graphics.PorterDuff;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.DrawableContainer;
-import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.InsetDrawable;
 
 /**
  * Implementation of drawable compatibility that can call L APIs.
@@ -51,16 +48,8 @@
 
     public static Drawable wrapForTinting(final Drawable drawable) {
         if (!(drawable instanceof DrawableWrapperLollipop)) {
-            return new DrawableWrapperLollipop(drawable, shouldForceCompatTinting(drawable));
+            return new DrawableWrapperLollipop(drawable);
         }
         return drawable;
     }
-
-    private static boolean shouldForceCompatTinting(Drawable drawable) {
-        // GradientDrawable on Lollipop does not support tinting, so we'll use our compatible
-        // functionality instead. We also do the same for DrawableContainers and DrawableWrappers
-        // since they may contain GradientDrawable instances.
-        return drawable instanceof GradientDrawable || drawable instanceof DrawableContainer
-                || drawable instanceof InsetDrawable;
-    }
 }
diff --git a/v4/api21/android/support/v4/graphics/drawable/DrawableWrapperLollipop.java b/v4/api21/android/support/v4/graphics/drawable/DrawableWrapperLollipop.java
index 9533afd..9f3a99e 100644
--- a/v4/api21/android/support/v4/graphics/drawable/DrawableWrapperLollipop.java
+++ b/v4/api21/android/support/v4/graphics/drawable/DrawableWrapperLollipop.java
@@ -22,18 +22,20 @@
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.DrawableContainer;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.InsetDrawable;
+import android.os.Build;
+import android.support.annotation.Nullable;
 
 class DrawableWrapperLollipop extends DrawableWrapperKitKat {
 
-    private final boolean mUseCompatTinting;
-
     DrawableWrapperLollipop(Drawable drawable) {
-        this(drawable, false);
+        super(drawable);
     }
 
-    DrawableWrapperLollipop(Drawable drawable, boolean useCompatTinting) {
-        super(drawable);
-        mUseCompatTinting = useCompatTinting;
+    DrawableWrapperLollipop(DrawableWrapperState state, Resources resources) {
+        super(state, resources);
     }
 
     @Override
@@ -52,23 +54,13 @@
     }
 
     @Override
-    public void applyTheme(Resources.Theme t) {
-        mDrawable.applyTheme(t);
-    }
-
-    @Override
-    public boolean canApplyTheme() {
-        return mDrawable.canApplyTheme();
-    }
-
-    @Override
     public Rect getDirtyBounds() {
         return mDrawable.getDirtyBounds();
     }
 
     @Override
     public void setTintList(ColorStateList tint) {
-        if (mUseCompatTinting) {
+        if (isCompatTintEnabled()) {
             setCompatTintList(tint);
         } else {
             mDrawable.setTintList(tint);
@@ -77,7 +69,7 @@
 
     @Override
     public void setTint(int tintColor) {
-        if (mUseCompatTinting) {
+        if (isCompatTintEnabled()) {
             setCompatTint(tintColor);
         } else {
             mDrawable.setTint(tintColor);
@@ -86,7 +78,7 @@
 
     @Override
     public void setTintMode(PorterDuff.Mode tintMode) {
-        if (mUseCompatTinting) {
+        if (isCompatTintEnabled()) {
             setCompatTintMode(tintMode);
         } else {
             mDrawable.setTintMode(tintMode);
@@ -106,6 +98,28 @@
 
     @Override
     protected boolean isCompatTintEnabled() {
-        return mUseCompatTinting;
+        if (Build.VERSION.SDK_INT == 21) {
+            final Drawable drawable = mDrawable;
+            return drawable instanceof GradientDrawable || drawable instanceof DrawableContainer
+                    || drawable instanceof InsetDrawable;
+        }
+        return false;
+    }
+
+    @Override
+    DrawableWrapperState mutateConstantState() {
+        return new DrawableWrapperStateLollipop(mState, null);
+    }
+
+    private static class DrawableWrapperStateLollipop extends DrawableWrapperState {
+        DrawableWrapperStateLollipop(@Nullable DrawableWrapperState orig,
+                @Nullable Resources res) {
+            super(orig, res);
+        }
+
+        @Override
+        public Drawable newDrawable(@Nullable Resources res) {
+            return new DrawableWrapperLollipop(this, res);
+        }
     }
 }
diff --git a/v4/api23/android/support/v4/widget/TextViewCompatApi23.java b/v4/api23/android/support/v4/widget/TextViewCompatApi23.java
index 8370bfc..ad21409 100644
--- a/v4/api23/android/support/v4/widget/TextViewCompatApi23.java
+++ b/v4/api23/android/support/v4/widget/TextViewCompatApi23.java
@@ -18,10 +18,11 @@
 
 import android.support.annotation.IdRes;
 import android.support.annotation.NonNull;
+import android.support.annotation.StyleRes;
 import android.widget.TextView;
 
 class TextViewCompatApi23 {
-    public static void setTextAppearance(@NonNull TextView textView, @IdRes int resId) {
+    public static void setTextAppearance(@NonNull TextView textView, @StyleRes int resId) {
         textView.setTextAppearance(resId);
     }
 }
diff --git a/v4/build.gradle b/v4/build.gradle
index 5abdbb9..1bb53ca 100644
--- a/v4/build.gradle
+++ b/v4/build.gradle
@@ -1,4 +1,4 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
 archivesBaseName = 'support-v4'
 
 // create a jar task for the code internal implementation
@@ -65,8 +65,12 @@
     // when manipulating the libraryVariants.
     compile files(internalJar.archivePath)
 
-    androidTestCompile 'com.android.support.test:testing-support-lib:0.1'
-    androidTestCompile 'com.android.support.test.espresso:espresso-core:2.0'
+    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'
 }
 
@@ -104,7 +108,7 @@
 
     testOptions {
         unitTests.returnDefaultValues = true
-        compileSdkVersion 'current'
+        compileSdkVersion project.ext.currentSdk
     }
 }
 
diff --git a/v4/donut/android/support/v4/graphics/drawable/DrawableWrapperDonut.java b/v4/donut/android/support/v4/graphics/drawable/DrawableWrapperDonut.java
index f742c15..d04a2ae 100644
--- a/v4/donut/android/support/v4/graphics/drawable/DrawableWrapperDonut.java
+++ b/v4/donut/android/support/v4/graphics/drawable/DrawableWrapperDonut.java
@@ -17,12 +17,15 @@
 package android.support.v4.graphics.drawable;
 
 import android.content.res.ColorStateList;
+import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.ColorFilter;
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 
 /**
  * Drawable which delegates all calls to it's wrapped {@link android.graphics.drawable.Drawable}.
@@ -32,19 +35,49 @@
  */
 class DrawableWrapperDonut extends Drawable implements Drawable.Callback, DrawableWrapper {
 
-    static final PorterDuff.Mode DEFAULT_MODE = PorterDuff.Mode.SRC_IN;
-
-    private ColorStateList mTintList;
-    private PorterDuff.Mode mTintMode = DEFAULT_MODE;
+    static final PorterDuff.Mode DEFAULT_TINT_MODE = PorterDuff.Mode.SRC_IN;
 
     private int mCurrentColor;
     private PorterDuff.Mode mCurrentMode;
     private boolean mColorFilterSet;
 
+    DrawableWrapperState mState;
+    private boolean mMutated;
+
     Drawable mDrawable;
 
-    DrawableWrapperDonut(Drawable drawable) {
-        setWrappedDrawable(drawable);
+    DrawableWrapperDonut(@NonNull DrawableWrapperState state, @Nullable Resources res) {
+        mState = state;
+        updateLocalState(res);
+    }
+    /**
+     * Creates a new wrapper around the specified drawable.
+     *
+     * @param dr the drawable to wrap
+     */
+    DrawableWrapperDonut(@Nullable Drawable dr) {
+        mState = mutateConstantState();
+        mDrawable = dr;
+    }
+
+    /**
+     * Initializes local dynamic properties from state. This should be called
+     * after significant state changes, e.g. from the One True Constructor and
+     * after inflating or applying a theme.
+     */
+    private void updateLocalState(@Nullable Resources res) {
+        if (mState != null && mState.mDrawableState != null) {
+            final Drawable dr = newDrawableFromState(mState.mDrawableState, res);
+            setWrappedDrawable(dr);
+        }
+    }
+
+    /**
+     * Allows us to call ConstantState.newDrawable(*) is a API safe way
+     */
+    protected Drawable newDrawableFromState(@NonNull Drawable.ConstantState state,
+            @Nullable Resources res) {
+        return state.newDrawable();
     }
 
     @Override
@@ -66,7 +99,9 @@
 
     @Override
     public int getChangingConfigurations() {
-        return mDrawable.getChangingConfigurations();
+        return super.getChangingConfigurations()
+                | (mState != null ? mState.getChangingConfigurations() : 0)
+                | mDrawable.getChangingConfigurations();
     }
 
     @Override
@@ -91,7 +126,7 @@
 
     @Override
     public boolean isStateful() {
-        final ColorStateList tintList = isCompatTintEnabled() ? mTintList : null;
+        final ColorStateList tintList = isCompatTintEnabled() ? mState.mTint : null;
         return (tintList != null && tintList.isStateful()) || mDrawable.isStateful();
     }
 
@@ -153,18 +188,43 @@
     }
 
     @Override
-    public Drawable mutate() {
-        Drawable wrapped = mDrawable;
-        Drawable mutated = wrapped.mutate();
-        if (mutated != wrapped) {
-            // If mutate() returned a new instance, update our reference
-            setWrappedDrawable(mutated);
+    @Nullable
+    public ConstantState getConstantState() {
+        if (mState != null && mState.canConstantState()) {
+            mState.mChangingConfigurations = getChangingConfigurations();
+            return mState;
         }
-        // We return ourselves, since only the wrapped drawable needs to mutate
+        return null;
+    }
+
+    @Override
+    public Drawable mutate() {
+        if (!mMutated && super.mutate() == this) {
+            mState = mutateConstantState();
+            if (mDrawable != null) {
+                mDrawable.mutate();
+            }
+            if (mState != null) {
+                mState.mDrawableState = mDrawable != null ? mDrawable.getConstantState() : null;
+            }
+            mMutated = true;
+        }
         return this;
     }
 
     /**
+     * Mutates the constant state and returns the new state.
+     * <p>
+     * This method should never call the super implementation; it should always
+     * mutate and return its own constant state.
+     *
+     * @return the new state
+     */
+    DrawableWrapperState mutateConstantState() {
+        return new DrawableWrapperStateDonut(mState, null);
+    }
+
+    /**
      * {@inheritDoc}
      */
     public void invalidateDrawable(Drawable who) {
@@ -197,18 +257,14 @@
 
     @Override
     public void setCompatTintList(ColorStateList tint) {
-        if (mTintList != tint) {
-            mTintList = tint;
-            updateTint(getState());
-        }
+        mState.mTint = tint;
+        updateTint(getState());
     }
 
     @Override
     public void setCompatTintMode(PorterDuff.Mode tintMode) {
-        if (mTintMode != tintMode) {
-            mTintMode = tintMode;
-            updateTint(getState());
-        }
+        mState.mTintMode = tintMode;
+        updateTint(getState());
     }
 
     private boolean updateTint(int[] state) {
@@ -217,12 +273,15 @@
             return false;
         }
 
-        if (mTintList != null && mTintMode != null) {
-            final int color = mTintList.getColorForState(state, mTintList.getDefaultColor());
-            if (!mColorFilterSet || color != mCurrentColor || mTintMode != mCurrentMode) {
-                setColorFilter(color, mTintMode);
+        final ColorStateList tintList = mState.mTint;
+        final PorterDuff.Mode tintMode = mState.mTintMode;
+
+        if (tintList != null && tintMode != null) {
+            final int color = tintList.getColorForState(state, tintList.getDefaultColor());
+            if (!mColorFilterSet || color != mCurrentColor || tintMode != mCurrentMode) {
+                setColorFilter(color, tintMode);
                 mCurrentColor = color;
-                mCurrentMode = mTintMode;
+                mCurrentMode = tintMode;
                 mColorFilterSet = true;
                 return true;
             }
@@ -243,25 +302,25 @@
     /**
      * Sets the current wrapped {@link Drawable}
      */
-    public void setWrappedDrawable(Drawable drawable) {
+    public void setWrappedDrawable(Drawable dr) {
         if (mDrawable != null) {
             mDrawable.setCallback(null);
         }
-        mDrawable = null;
 
-        if (drawable != null) {
-            // Copy over the bounds from the drawable
-            setBounds(drawable.getBounds());
-            // Set ourselves as the callback for invalidations
-            drawable.setCallback(this);
-        } else {
-            // Clear our bounds
-            setBounds(0, 0, 0, 0);
+        mDrawable = dr;
+
+        if (dr != null) {
+            dr.setCallback(this);
+            // Only call setters for data that's stored in the base Drawable.
+            dr.setVisible(isVisible(), true);
+            dr.setState(getState());
+            dr.setLevel(getLevel());
+            dr.setBounds(getBounds());
+            if (mState != null) {
+                mState.mDrawableState = dr.getConstantState();
+            }
         }
 
-        mDrawable = drawable;
-
-        // Invalidate ourselves
         invalidateSelf();
     }
 
@@ -269,4 +328,50 @@
         // It's enabled by default on Donut
         return true;
     }
+
+    protected static abstract class DrawableWrapperState extends Drawable.ConstantState {
+        int mChangingConfigurations;
+        Drawable.ConstantState mDrawableState;
+
+        ColorStateList mTint = null;
+        PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE;
+
+        DrawableWrapperState(@Nullable DrawableWrapperState orig, @Nullable Resources res) {
+            if (orig != null) {
+                mChangingConfigurations = orig.mChangingConfigurations;
+                mDrawableState = orig.mDrawableState;
+                mTint = orig.mTint;
+                mTintMode = orig.mTintMode;
+            }
+        }
+
+        @Override
+        public Drawable newDrawable() {
+            return newDrawable(null);
+        }
+
+        public abstract Drawable newDrawable(@Nullable Resources res);
+
+        @Override
+        public int getChangingConfigurations() {
+            return mChangingConfigurations
+                    | (mDrawableState != null ? mDrawableState.getChangingConfigurations() : 0);
+        }
+
+        boolean canConstantState() {
+            return mDrawableState != null;
+        }
+    }
+
+    private static class DrawableWrapperStateDonut extends DrawableWrapperState {
+        DrawableWrapperStateDonut(
+                @Nullable DrawableWrapperState orig, @Nullable Resources res) {
+            super(orig, res);
+        }
+
+        @Override
+        public Drawable newDrawable(@Nullable Resources res) {
+            return new DrawableWrapperDonut(this, res);
+        }
+    }
 }
diff --git a/v4/api22/android/support/v4/graphics/drawable/DrawableCompatApi22.java b/v4/eclair/android/support/v4/graphics/drawable/DrawableCompatEclair.java
similarity index 68%
rename from v4/api22/android/support/v4/graphics/drawable/DrawableCompatApi22.java
rename to v4/eclair/android/support/v4/graphics/drawable/DrawableCompatEclair.java
index e0455bb..01316a8 100644
--- a/v4/api22/android/support/v4/graphics/drawable/DrawableCompatApi22.java
+++ b/v4/eclair/android/support/v4/graphics/drawable/DrawableCompatEclair.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.
@@ -19,16 +19,13 @@
 import android.graphics.drawable.Drawable;
 
 /**
- * Implementation of drawable compatibility that can call Lollipop-MR1 APIs.
+ * Implementation of drawable compatibility that can call Eclair APIs.
  */
-class DrawableCompatApi22 {
-
+class DrawableCompatEclair {
     public static Drawable wrapForTinting(Drawable drawable) {
-        // We need to wrap to force an invalidation on any state change
-        if (!(drawable instanceof DrawableWrapperLollipop)) {
-            return new DrawableWrapperLollipop(drawable);
+        if (!(drawable instanceof DrawableWrapperEclair)) {
+            return new DrawableWrapperEclair(drawable);
         }
         return drawable;
     }
-
 }
diff --git a/v4/eclair/android/support/v4/graphics/drawable/DrawableWrapperEclair.java b/v4/eclair/android/support/v4/graphics/drawable/DrawableWrapperEclair.java
new file mode 100644
index 0000000..11a65ee
--- /dev/null
+++ b/v4/eclair/android/support/v4/graphics/drawable/DrawableWrapperEclair.java
@@ -0,0 +1,60 @@
+/*
+ * 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.graphics.drawable;
+
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.Nullable;
+
+class DrawableWrapperEclair extends DrawableWrapperDonut {
+
+    DrawableWrapperEclair(Drawable drawable) {
+        super(drawable);
+    }
+
+    DrawableWrapperEclair(DrawableWrapperState state, Resources resources) {
+        super(state, resources);
+    }
+
+    @Override
+    DrawableWrapperState mutateConstantState() {
+        return new DrawableWrapperStateEclair(mState, null);
+    }
+
+    @Override
+    protected Drawable newDrawableFromState(Drawable.ConstantState state, Resources res) {
+        return state.newDrawable(res);
+    }
+
+    private static class DrawableWrapperStateEclair extends DrawableWrapperState {
+        DrawableWrapperStateEclair(@Nullable DrawableWrapperState orig,
+                @Nullable Resources res) {
+            super(orig, res);
+        }
+
+        @Override
+        public Drawable newDrawable(@Nullable Resources res) {
+            return new DrawableWrapperEclair(this, res);
+        }
+    }
+}
\ No newline at end of file
diff --git a/v4/honeycomb/android/support/v4/graphics/drawable/DrawableWrapperHoneycomb.java b/v4/honeycomb/android/support/v4/graphics/drawable/DrawableWrapperHoneycomb.java
index f9fd7d8..18970ed 100644
--- a/v4/honeycomb/android/support/v4/graphics/drawable/DrawableWrapperHoneycomb.java
+++ b/v4/honeycomb/android/support/v4/graphics/drawable/DrawableWrapperHoneycomb.java
@@ -16,7 +16,9 @@
 
 package android.support.v4.graphics.drawable;
 
+import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
+import android.support.annotation.Nullable;
 
 class DrawableWrapperHoneycomb extends DrawableWrapperDonut {
 
@@ -24,8 +26,29 @@
         super(drawable);
     }
 
+    DrawableWrapperHoneycomb(DrawableWrapperState state, Resources resources) {
+        super(state, resources);
+    }
+
     @Override
     public void jumpToCurrentState() {
         mDrawable.jumpToCurrentState();
     }
+
+    @Override
+    DrawableWrapperState mutateConstantState() {
+        return new DrawableWrapperStateHoneycomb(mState, null);
+    }
+
+    private static class DrawableWrapperStateHoneycomb extends DrawableWrapperState {
+        DrawableWrapperStateHoneycomb(@Nullable DrawableWrapperState orig,
+                @Nullable Resources res) {
+            super(orig, res);
+        }
+
+        @Override
+        public Drawable newDrawable(@Nullable Resources res) {
+            return new DrawableWrapperHoneycomb(this, res);
+        }
+    }
 }
diff --git a/v4/java/android/support/v4/app/FragmentActivity.java b/v4/java/android/support/v4/app/FragmentActivity.java
index 4d6585a..0fbe357 100644
--- a/v4/java/android/support/v4/app/FragmentActivity.java
+++ b/v4/java/android/support/v4/app/FragmentActivity.java
@@ -784,7 +784,9 @@
      */
     @Override
     public void startActivityForResult(Intent intent, int requestCode) {
-        if (mStartedActivityFromFragment) {
+        // If this was started from a Fragment we've already checked the upper 16 bits were not in
+        // use, and then repurposed them for the Fragment's index.
+        if (!mStartedActivityFromFragment) {
             if (requestCode != -1 && (requestCode&0xffff0000) != 0) {
                 throw new IllegalArgumentException("Can only use lower 16 bits for requestCode");
             }
diff --git a/v4/java/android/support/v4/content/ContextCompat.java b/v4/java/android/support/v4/content/ContextCompat.java
index 29629e7..16ec1da 100644
--- a/v4/java/android/support/v4/content/ContextCompat.java
+++ b/v4/java/android/support/v4/content/ContextCompat.java
@@ -403,7 +403,7 @@
      *
      * @see android.content.Context.getFilesDir
      */
-    public static File getNoBackupFilesDir(Context context) {
+    public final File getNoBackupFilesDir(Context context) {
         final int version = Build.VERSION.SDK_INT;
         if (version >= 21) {
             return ContextCompatApi21.getNoBackupFilesDir(context);
diff --git a/v4/java/android/support/v4/graphics/ColorUtils.java b/v4/java/android/support/v4/graphics/ColorUtils.java
index 2a3074e..85768f6 100644
--- a/v4/java/android/support/v4/graphics/ColorUtils.java
+++ b/v4/java/android/support/v4/graphics/ColorUtils.java
@@ -22,7 +22,6 @@
 import android.support.annotation.IntRange;
 import android.support.annotation.NonNull;
 import android.support.annotation.VisibleForTesting;
-import android.util.TypedValue;
 
 /**
  * A set of color-related utility methods, building upon those available in {@code Color}.
@@ -75,11 +74,7 @@
      */
     @FloatRange(from = 0.0, to = 1.0)
     public static double calculateLuminance(@ColorInt int color) {
-        double[] result = TEMP_ARRAY.get();
-        if (result == null) {
-            result = new double[3];
-            TEMP_ARRAY.set(result);
-        }
+        final double[] result = getTempDouble3Array();
         colorToXYZ(color, result);
         // Luminance is the Y component
         return result[1] / 100;
@@ -114,10 +109,10 @@
      * have a contrast value of at least {@code minContrastRatio} when compared to
      * {@code background}.
      *
-     * @param foreground       the foreground color.
-     * @param background       the background color. Should be opaque.
-     * @param minContrastRatio the minimum contrast ratio.
-     * @return the alpha value in the range 0-255, or -1 if no value could be calculated.
+     * @param foreground       the foreground color
+     * @param background       the opaque background color
+     * @param minContrastRatio the minimum contrast ratio
+     * @return the alpha value in the range 0-255, or -1 if no value could be calculated
      */
     public static int calculateMinimumAlpha(@ColorInt int foreground, @ColorInt int background,
             float minContrastRatio) {
@@ -162,19 +157,19 @@
     /**
      * Convert RGB components to HSL (hue-saturation-lightness).
      * <ul>
-     * <li>hsl[0] is Hue [0 .. 360)</li>
-     * <li>hsl[1] is Saturation [0...1]</li>
-     * <li>hsl[2] is Lightness [0...1]</li>
+     * <li>outHsl[0] is Hue [0 .. 360)</li>
+     * <li>outHsl[1] is Saturation [0...1]</li>
+     * <li>outHsl[2] is Lightness [0...1]</li>
      * </ul>
      *
-     * @param r   red component value [0..255]
-     * @param g   green component value [0..255]
-     * @param b   blue component value [0..255]
-     * @param hsl 3 element array which holds the resulting HSL components.
+     * @param r      red component value [0..255]
+     * @param g      green component value [0..255]
+     * @param b      blue component value [0..255]
+     * @param outHsl 3-element array which holds the resulting HSL components
      */
     public static void RGBToHSL(@IntRange(from = 0x0, to = 0xFF) int r,
             @IntRange(from = 0x0, to = 0xFF) int g, @IntRange(from = 0x0, to = 0xFF) int b,
-            @NonNull float[] hsl) {
+            @NonNull float[] outHsl) {
         final float rf = r / 255f;
         final float gf = g / 255f;
         final float bf = b / 255f;
@@ -206,24 +201,24 @@
             h += 360f;
         }
 
-        hsl[0] = constrain(h, 0f, 360f);
-        hsl[1] = constrain(s, 0f, 1f);
-        hsl[2] = constrain(l, 0f, 1f);
+        outHsl[0] = constrain(h, 0f, 360f);
+        outHsl[1] = constrain(s, 0f, 1f);
+        outHsl[2] = constrain(l, 0f, 1f);
     }
 
     /**
      * Convert the ARGB color to its HSL (hue-saturation-lightness) components.
      * <ul>
-     * <li>hsl[0] is Hue [0 .. 360)</li>
-     * <li>hsl[1] is Saturation [0...1]</li>
-     * <li>hsl[2] is Lightness [0...1]</li>
+     * <li>outHsl[0] is Hue [0 .. 360)</li>
+     * <li>outHsl[1] is Saturation [0...1]</li>
+     * <li>outHsl[2] is Lightness [0...1]</li>
      * </ul>
      *
-     * @param color the ARGB color to convert. The alpha component is ignored.
-     * @param hsl 3 element array which holds the resulting HSL components.
+     * @param color  the ARGB color to convert. The alpha component is ignored
+     * @param outHsl 3-element array which holds the resulting HSL components
      */
-    public static void colorToHSL(@ColorInt int color, @NonNull float[] hsl) {
-        RGBToHSL(Color.red(color), Color.green(color), Color.blue(color), hsl);
+    public static void colorToHSL(@ColorInt int color, @NonNull float[] outHsl) {
+        RGBToHSL(Color.red(color), Color.green(color), Color.blue(color), outHsl);
     }
 
     /**
@@ -235,7 +230,7 @@
      * </ul>
      * If hsv values are out of range, they are pinned.
      *
-     * @param hsl 3 element array which holds the input HSL components.
+     * @param hsl 3-element array which holds the input HSL components
      * @return the resulting RGB color
      */
     @ColorInt
@@ -308,35 +303,35 @@
     /**
      * Convert the ARGB color to its CIE Lab representative components.
      *
-     * @param color the ARGB color to convert. The alpha component is ignored.
-     * @param result 3 element array which holds the resulting LAB components.
+     * @param color  the ARGB color to convert. The alpha component is ignored
+     * @param outLab 3-element array which holds the resulting LAB components
      */
-    public static void colorToLAB(@ColorInt int color, @NonNull double[] result) {
-        RGBToLAB(Color.red(color), Color.green(color), Color.blue(color), result);
+    public static void colorToLAB(@ColorInt int color, @NonNull double[] outLab) {
+        RGBToLAB(Color.red(color), Color.green(color), Color.blue(color), outLab);
     }
 
     /**
      * Convert RGB components to its CIE Lab representative components.
      *
      * <ul>
-     * <li>result[0] is L [0 ...1)</li>
-     * <li>result[1] is a [-128...127)</li>
-     * <li>result[2] is b [-128...127)</li>
+     * <li>outLab[0] is L [0 ...1)</li>
+     * <li>outLab[1] is a [-128...127)</li>
+     * <li>outLab[2] is b [-128...127)</li>
      * </ul>
      *
-     * @param r   red component value [0..255)
-     * @param g   green component value [0..255)
-     * @param b   blue component value [0..255)
-     * @param result 3 element array which holds the resulting LAB components.
+     * @param r      red component value [0..255]
+     * @param g      green component value [0..255]
+     * @param b      blue component value [0..255]
+     * @param outLab 3-element array which holds the resulting LAB components
      */
     public static void RGBToLAB(@IntRange(from = 0x0, to = 0xFF) int r,
             @IntRange(from = 0x0, to = 0xFF) int g, @IntRange(from = 0x0, to = 0xFF) int b,
-            @NonNull double[] result) {
+            @NonNull double[] outLab) {
         // First we convert RGB to XYZ
-        RGBToXYZ(r, g, b, result);
-        // result now contains XYZ
-        XYZToLAB(result[0], result[1], result[2], result);
-        // result now contains LAB representation
+        RGBToXYZ(r, g, b, outLab);
+        // outLab now contains XYZ
+        XYZToLAB(outLab[0], outLab[1], outLab[2], outLab);
+        // outLab now contains LAB representation
     }
 
     /**
@@ -346,16 +341,16 @@
      * 2° Standard Observer (1931).</p>
      *
      * <ul>
-     * <li>result[0] is X [0 ...95.047)</li>
-     * <li>result[1] is Y [0...100)</li>
-     * <li>result[2] is Z [0...108.883)</li>
+     * <li>outXyz[0] is X [0 ...95.047)</li>
+     * <li>outXyz[1] is Y [0...100)</li>
+     * <li>outXyz[2] is Z [0...108.883)</li>
      * </ul>
      *
-     * @param color the ARGB color to convert. The alpha component is ignored.
-     * @param result 3 element array which holds the resulting LAB components.
+     * @param color  the ARGB color to convert. The alpha component is ignored
+     * @param outXyz 3-element array which holds the resulting LAB components
      */
-    public static void colorToXYZ(@ColorInt int color, @NonNull double[] result) {
-        RGBToXYZ(Color.red(color), Color.green(color), Color.blue(color), result);
+    public static void colorToXYZ(@ColorInt int color, @NonNull double[] outXyz) {
+        RGBToXYZ(Color.red(color), Color.green(color), Color.blue(color), outXyz);
     }
 
     /**
@@ -365,21 +360,21 @@
      * 2° Standard Observer (1931).</p>
      *
      * <ul>
-     * <li>result[0] is X [0 ...95.047)</li>
-     * <li>result[1] is Y [0...100)</li>
-     * <li>result[2] is Z [0...108.883)</li>
+     * <li>outXyz[0] is X [0 ...95.047)</li>
+     * <li>outXyz[1] is Y [0...100)</li>
+     * <li>outXyz[2] is Z [0...108.883)</li>
      * </ul>
      *
-     * @param r   red component value [0..255)
-     * @param g   green component value [0..255)
-     * @param b   blue component value [0..255)
-     * @param result 3 element array which holds the resulting XYZ components.
+     * @param r      red component value [0..255]
+     * @param g      green component value [0..255]
+     * @param b      blue component value [0..255]
+     * @param outXyz 3-element array which holds the resulting XYZ components
      */
     public static void RGBToXYZ(@IntRange(from = 0x0, to = 0xFF) int r,
             @IntRange(from = 0x0, to = 0xFF) int g, @IntRange(from = 0x0, to = 0xFF) int b,
-            @NonNull double[] result) {
-        if (result.length != 3) {
-            throw new IllegalArgumentException("result must have a length of 3.");
+            @NonNull double[] outXyz) {
+        if (outXyz.length != 3) {
+            throw new IllegalArgumentException("outXyz must have a length of 3.");
         }
 
         double sr = r / 255.0;
@@ -389,9 +384,9 @@
         double sb = b / 255.0;
         sb = sb < 0.04045 ? sb / 12.92 : Math.pow((sb + 0.055) / 1.055, 2.4);
 
-        result[0] = 100 * (sr * 0.4124 + sg * 0.3576 + sb * 0.1805);
-        result[1] = 100 * (sr * 0.2126 + sg * 0.7152 + sb * 0.0722);
-        result[2] = 100 * (sr * 0.0193 + sg * 0.1192 + sb * 0.9505);
+        outXyz[0] = 100 * (sr * 0.4124 + sg * 0.3576 + sb * 0.1805);
+        outXyz[1] = 100 * (sr * 0.2126 + sg * 0.7152 + sb * 0.0722);
+        outXyz[2] = 100 * (sr * 0.0193 + sg * 0.1192 + sb * 0.9505);
     }
 
     /**
@@ -401,29 +396,29 @@
      * 2° Standard Observer (1931).</p>
      *
      * <ul>
-     * <li>result[0] is L [0 ...1)</li>
-     * <li>result[1] is a [-128...127)</li>
-     * <li>result[2] is b [-128...127)</li>
+     * <li>outLab[0] is L [0 ...1)</li>
+     * <li>outLab[1] is a [-128...127)</li>
+     * <li>outLab[2] is b [-128...127)</li>
      * </ul>
      *
-     * @param x X component value [0...95.047)
-     * @param y Y component value [0...100)
-     * @param z Z component value [0...108.883)
-     * @param result 3 element array which holds the resulting Lab components.
+     * @param x      X component value [0...95.047)
+     * @param y      Y component value [0...100)
+     * @param z      Z component value [0...108.883)
+     * @param outLab 3-element array which holds the resulting Lab components
      */
     public static void XYZToLAB(@FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_X) double x,
             @FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Y) double y,
             @FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Z) double z,
-            @NonNull double[] result) {
-        if (result.length != 3) {
-            throw new IllegalArgumentException("result must have a length of 3.");
+            @NonNull double[] outLab) {
+        if (outLab.length != 3) {
+            throw new IllegalArgumentException("outLab must have a length of 3.");
         }
         x = pivotXyzComponent(x / XYZ_WHITE_REFERENCE_X);
         y = pivotXyzComponent(y / XYZ_WHITE_REFERENCE_Y);
         z = pivotXyzComponent(z / XYZ_WHITE_REFERENCE_Z);
-        result[0] = Math.max(0, 116 * y - 16);
-        result[1] = 500 * (x - y);
-        result[2] = 200 * (y - z);
+        outLab[0] = Math.max(0, 116 * y - 16);
+        outLab[1] = 500 * (x - y);
+        outLab[2] = 200 * (y - z);
     }
 
     /**
@@ -433,20 +428,20 @@
      * 2° Standard Observer (1931).</p>
      *
      * <ul>
-     * <li>result[0] is X [0 ...95.047)</li>
-     * <li>result[1] is Y [0...100)</li>
-     * <li>result[2] is Z [0...108.883)</li>
+     * <li>outXyz[0] is X [0 ...95.047)</li>
+     * <li>outXyz[1] is Y [0...100)</li>
+     * <li>outXyz[2] is Z [0...108.883)</li>
      * </ul>
      *
-     * @param l L component value [0...100)
-     * @param a A component value [-128...127)
-     * @param b B component value [-128...127)
-     * @param result 3 element array which holds the resulting XYZ components.
+     * @param l      L component value [0...100)
+     * @param a      A component value [-128...127)
+     * @param b      B component value [-128...127)
+     * @param outXyz 3-element array which holds the resulting XYZ components
      */
     public static void LABToXYZ(@FloatRange(from = 0f, to = 100) final double l,
             @FloatRange(from = -128, to = 127) final double a,
             @FloatRange(from = -128, to = 127) final double b,
-            @NonNull double[] result) {
+            @NonNull double[] outXyz) {
         final double fy = (l + 16) / 116;
         final double fx = a / 500 + fy;
         final double fz = fy - b / 200;
@@ -458,9 +453,9 @@
         tmp = Math.pow(fz, 3);
         final double zr = tmp > XYZ_EPSILON ? tmp : (116 * fz - 16) / XYZ_KAPPA;
 
-        result[0] = xr * XYZ_WHITE_REFERENCE_X;
-        result[1] = yr * XYZ_WHITE_REFERENCE_Y;
-        result[2] = zr * XYZ_WHITE_REFERENCE_Z;
+        outXyz[0] = xr * XYZ_WHITE_REFERENCE_X;
+        outXyz[1] = yr * XYZ_WHITE_REFERENCE_Y;
+        outXyz[2] = zr * XYZ_WHITE_REFERENCE_Z;
     }
 
     /**
@@ -495,16 +490,16 @@
     /**
      * Converts a color from CIE Lab to its RGB representation.
      *
-     * @param l L component value [0...100)
-     * @param a A component value [-128...127)
-     * @param b B component value [-128...127)
+     * @param l L component value [0...100]
+     * @param a A component value [-128...127]
+     * @param b B component value [-128...127]
      * @return int containing the RGB representation
      */
     @ColorInt
     public static int LABToColor(@FloatRange(from = 0f, to = 100) final double l,
             @FloatRange(from = -128, to = 127) final double a,
             @FloatRange(from = -128, to = 127) final double b) {
-        final double[] result = new double[3];
+        final double[] result = getTempDouble3Array();
         LABToXYZ(l, a, b, result);
         return XYZToColor(result[0], result[1], result[2]);
     }
@@ -535,8 +530,12 @@
     /**
      * Blend between two ARGB colors using the given ratio.
      *
-     * @param ratio of which to blend. 0.0 will return {@code color1}, 0.5 will give an even blend,
-     *              1.0 will return {@code color2}.
+     * <p>A blend ratio of 0.0 will result in {@code color1}, 0.5 will give an even blend,
+     * 1.0 will result in {@code color2}.</p>
+     *
+     * @param color1 the first ARGB color
+     * @param color2 the second ARGB color
+     * @param ratio  the blend ratio of {@code color1} to {@code color2}
      */
     @ColorInt
     public static int blendARGB(@ColorInt int color1, @ColorInt int color2,
@@ -553,43 +552,46 @@
      * Blend between {@code hsl1} and {@code hsl2} using the given ratio. This will interpolate
      * the hue using the shortest angle.
      *
-     * @param hsl1 3 element array which holds the first HSL color.
-     * @param hsl2 3 element array which holds the second HSL color.
-     * @param result 3 element array which holds the resulting HSL components.
-     * @param ratio of which to blend. 0.0 will result in {@code hsl1},
-     *              0.5 will give an even blend, 1.0 will return {@code hsl2}.
+     * <p>A blend ratio of 0.0 will result in {@code hsl1}, 0.5 will give an even blend,
+     * 1.0 will result in {@code hsl2}.</p>
+     *
+     * @param hsl1      3-element array which holds the first HSL color
+     * @param hsl2      3-element array which holds the second HSL color
+     * @param ratio     the blend ratio of {@code hsl1} to {@code hsl2}
+     * @param outResult 3-element array which holds the resulting HSL components
      */
     public static void blendHSL(@NonNull float[] hsl1, @NonNull float[] hsl2,
-            @NonNull float[] result, @FloatRange(from = 0.0, to = 1.0) float ratio) {
-        if (result.length != 3) {
+            @FloatRange(from = 0.0, to = 1.0) float ratio, @NonNull float[] outResult) {
+        if (outResult.length != 3) {
             throw new IllegalArgumentException("result must have a length of 3.");
         }
         final float inverseRatio = 1 - ratio;
         // Since hue is circular we will need to interpolate carefully
-        result[0] = circularInterpolate(hsl1[0], hsl2[0], ratio);
-        result[1] = hsl1[1] * inverseRatio + hsl2[1] * ratio;
-        result[2] = hsl1[2] * inverseRatio + hsl2[2] * ratio;
+        outResult[0] = circularInterpolate(hsl1[0], hsl2[0], ratio);
+        outResult[1] = hsl1[1] * inverseRatio + hsl2[1] * ratio;
+        outResult[2] = hsl1[2] * inverseRatio + hsl2[2] * ratio;
     }
 
     /**
      * Blend between two CIE-LAB colors using the given ratio.
      *
-     * @param lab1 3 element array which holds the first LAB color.
-     * @param lab2 3 element array which holds the second LAB color.
-     * @param result 3 element array which holds the resulting LAB components.
-     * @param ratio of which to blend. 0.0 will result in {@code lab1}, 0.5 will give an even blend,
-     *              1.0 will return {@code lab2}.
+     * <p>A blend ratio of 0.0 will result in {@code lab1}, 0.5 will give an even blend,
+     * 1.0 will result in {@code lab2}.</p>
+     *
+     * @param lab1      3-element array which holds the first LAB color
+     * @param lab2      3-element array which holds the second LAB color
+     * @param ratio     the blend ratio of {@code lab1} to {@code lab2}
+     * @param outResult 3-element array which holds the resulting LAB components
      */
-    public static void blendLAB(@NonNull double[] lab1,
-            @NonNull double[] lab2, @NonNull double[] result,
-            @FloatRange(from = 0.0, to = 1.0) double ratio) {
-        if (result.length != 3) {
-            throw new IllegalArgumentException("result must have a length of 3.");
+    public static void blendLAB(@NonNull double[] lab1, @NonNull double[] lab2,
+            @FloatRange(from = 0.0, to = 1.0) double ratio, @NonNull double[] outResult) {
+        if (outResult.length != 3) {
+            throw new IllegalArgumentException("outResult must have a length of 3.");
         }
         final double inverseRatio = 1 - ratio;
-        result[0] = lab1[0] * inverseRatio + lab2[0] * ratio;
-        result[1] = lab1[1] * inverseRatio + lab2[1] * ratio;
-        result[2] = lab1[2] * inverseRatio + lab2[2] * ratio;
+        outResult[0] = lab1[0] * inverseRatio + lab2[0] * ratio;
+        outResult[1] = lab1[1] * inverseRatio + lab2[1] * ratio;
+        outResult[2] = lab1[2] * inverseRatio + lab2[2] * ratio;
     }
 
     @VisibleForTesting
@@ -604,4 +606,13 @@
         return (a + ((b - a) * f)) % 360;
     }
 
+    private static double[] getTempDouble3Array() {
+        double[] result = TEMP_ARRAY.get();
+        if (result == null) {
+            result = new double[3];
+            TEMP_ARRAY.set(result);
+        }
+        return result;
+    }
+
 }
diff --git a/v4/java/android/support/v4/graphics/drawable/DrawableCompat.java b/v4/java/android/support/v4/graphics/drawable/DrawableCompat.java
index 97545ef..a24a0a0 100644
--- a/v4/java/android/support/v4/graphics/drawable/DrawableCompat.java
+++ b/v4/java/android/support/v4/graphics/drawable/DrawableCompat.java
@@ -103,9 +103,19 @@
     }
 
     /**
+     * Interface implementation for devices with at least v5 APIs.
+     */
+    static class EclairDrawableImpl extends BaseDrawableImpl {
+        @Override
+        public Drawable wrap(Drawable drawable) {
+            return DrawableCompatEclair.wrapForTinting(drawable);
+        }
+    }
+
+    /**
      * Interface implementation for devices with at least v11 APIs.
      */
-    static class HoneycombDrawableImpl extends BaseDrawableImpl {
+    static class HoneycombDrawableImpl extends EclairDrawableImpl {
         @Override
         public void jumpToCurrentState(Drawable drawable) {
             DrawableCompatHoneycomb.jumpToCurrentState(drawable);
@@ -186,19 +196,9 @@
     }
 
     /**
-     * Interface implementation for devices with at least L APIs.
-     */
-    static class LollipopMr1DrawableImpl extends LollipopDrawableImpl {
-        @Override
-        public Drawable wrap(Drawable drawable) {
-            return DrawableCompatApi22.wrapForTinting(drawable);
-        }
-    }
-
-    /**
      * Interface implementation for devices with at least M APIs.
      */
-    static class MDrawableImpl extends LollipopMr1DrawableImpl {
+    static class MDrawableImpl extends LollipopDrawableImpl {
         @Override
         public void setLayoutDirection(Drawable drawable, int layoutDirection) {
             DrawableCompatApi23.setLayoutDirection(drawable, layoutDirection);
@@ -208,6 +208,12 @@
         public int getLayoutDirection(Drawable drawable) {
             return DrawableCompatApi23.getLayoutDirection(drawable);
         }
+
+        @Override
+        public Drawable wrap(Drawable drawable) {
+            // No need to wrap on M+
+            return drawable;
+        }
     }
 
     /**
@@ -218,8 +224,6 @@
         final int version = android.os.Build.VERSION.SDK_INT;
         if (version >= 23) {
             IMPL = new MDrawableImpl();
-        } else if (version >= 22) {
-            IMPL = new LollipopMr1DrawableImpl();
         } else if (version >= 21) {
             IMPL = new LollipopDrawableImpl();
         } else if (version >= 19) {
@@ -228,6 +232,8 @@
             IMPL = new JellybeanMr1DrawableImpl();
         } else if (version >= 11) {
             IMPL = new HoneycombDrawableImpl();
+        } else if (version >= 5) {
+            IMPL = new EclairDrawableImpl();
         } else {
             IMPL = new BaseDrawableImpl();
         }
diff --git a/v4/java/android/support/v4/widget/DrawerLayout.java b/v4/java/android/support/v4/widget/DrawerLayout.java
index 82eb16c..0e2e829 100644
--- a/v4/java/android/support/v4/widget/DrawerLayout.java
+++ b/v4/java/android/support/v4/widget/DrawerLayout.java
@@ -688,6 +688,9 @@
      */
     @LockMode
     public int getDrawerLockMode(View drawerView) {
+        if (!isDrawerView(drawerView)) {
+            throw new IllegalArgumentException("View " + drawerView + " is not a drawer");
+        }
         final int drawerGravity = ((LayoutParams) drawerView.getLayoutParams()).gravity;
         return getDrawerLockMode(drawerGravity);
     }
diff --git a/v4/java/android/support/v4/widget/TextViewCompat.java b/v4/java/android/support/v4/widget/TextViewCompat.java
index 5df1ae6..7af3087 100644
--- a/v4/java/android/support/v4/widget/TextViewCompat.java
+++ b/v4/java/android/support/v4/widget/TextViewCompat.java
@@ -18,9 +18,11 @@
 
 import android.graphics.drawable.Drawable;
 import android.os.Build;
+import android.support.annotation.DrawableRes;
 import android.support.annotation.IdRes;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
+import android.support.annotation.StyleRes;
 import android.widget.TextView;
 
 /**
@@ -40,10 +42,11 @@
                 @Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end,
                 @Nullable Drawable bottom);
         void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
-                int start, int top, int end, int bottom);
+                @DrawableRes int start, @DrawableRes int top, @DrawableRes int end,
+                @DrawableRes int bottom);
         int getMaxLines(TextView textView);
         int getMinLines(TextView textView);
-        void setTextAppearance(@NonNull TextView textView, @IdRes int resId);
+        void setTextAppearance(@NonNull TextView textView, @StyleRes int resId);
     }
 
     static class BaseTextViewCompatImpl implements TextViewCompatImpl {
@@ -63,7 +66,8 @@
 
         @Override
         public void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
-                int start, int top, int end, int bottom) {
+                @DrawableRes int start, @DrawableRes int top, @DrawableRes int end,
+                @DrawableRes int bottom) {
             textView.setCompoundDrawablesWithIntrinsicBounds(start, top, end, bottom);
         }
 
@@ -78,7 +82,7 @@
         }
 
         @Override
-        public void setTextAppearance(TextView textView, int resId) {
+        public void setTextAppearance(TextView textView, @StyleRes int resId) {
             TextViewCompatDonut.setTextAppearance(textView, resId);
         }
     }
@@ -113,7 +117,8 @@
 
         @Override
         public void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
-                int start, int top, int end, int bottom) {
+                @DrawableRes int start, @DrawableRes int top, @DrawableRes int end,
+                @DrawableRes int bottom) {
             TextViewCompatJbMr1.setCompoundDrawablesRelativeWithIntrinsicBounds(textView,
                     start, top, end, bottom);
         }
@@ -138,7 +143,8 @@
 
         @Override
         public void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
-                int start, int top, int end, int bottom) {
+                @DrawableRes int start, @DrawableRes int top, @DrawableRes int end,
+                @DrawableRes int bottom) {
             TextViewCompatJbMr2.setCompoundDrawablesRelativeWithIntrinsicBounds(textView,
                     start, top, end, bottom);
         }
@@ -146,7 +152,7 @@
 
     static class Api23TextViewCompatImpl extends JbMr2TextViewCompatImpl {
         @Override
-        public void setTextAppearance(@NonNull TextView textView, @IdRes int resId) {
+        public void setTextAppearance(@NonNull TextView textView, @StyleRes int resId) {
             TextViewCompatApi23.setTextAppearance(textView, resId);
         }
     }
@@ -228,7 +234,8 @@
      * @attr ref android.R.styleable#TextView_drawableBottom
      */
     public static void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
-            int start, int top, int end, int bottom) {
+            @DrawableRes int start, @DrawableRes int top, @DrawableRes int end,
+            @DrawableRes int bottom) {
         IMPL.setCompoundDrawablesRelativeWithIntrinsicBounds(textView, start, top, end, bottom);
     }
 
@@ -259,7 +266,7 @@
      * @param textView The TextView against which to invoke the method.
      * @param resId    The resource identifier of the style to apply.
      */
-    public static void setTextAppearance(@NonNull TextView textView, @IdRes int resId) {
+    public static void setTextAppearance(@NonNull TextView textView, @StyleRes int resId) {
         IMPL.setTextAppearance(textView, resId);
     }
 }
diff --git a/v4/jellybean-mr1/android/support/v4/media/routing/MediaRouterJellybeanMr1.java b/v4/jellybean-mr1/android/support/v4/media/routing/MediaRouterJellybeanMr1.java
deleted file mode 100644
index 6e5cfd5..0000000
--- a/v4/jellybean-mr1/android/support/v4/media/routing/MediaRouterJellybeanMr1.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v4.media.routing;
-
-import android.content.Context;
-import android.hardware.display.DisplayManager;
-import android.os.Build;
-import android.os.Handler;
-import android.util.Log;
-import android.view.Display;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-class MediaRouterJellybeanMr1 extends MediaRouterJellybean {
-    private static final String TAG = "MediaRouterJellybeanMr1";
-
-    public static Object createCallback(Callback callback) {
-        return new CallbackProxy<Callback>(callback);
-    }
-
-    public static final class RouteInfo {
-        public static boolean isEnabled(Object routeObj) {
-            return ((android.media.MediaRouter.RouteInfo)routeObj).isEnabled();
-        }
-
-        public static Display getPresentationDisplay(Object routeObj) {
-            return ((android.media.MediaRouter.RouteInfo)routeObj).getPresentationDisplay();
-        }
-    }
-
-    public static interface Callback extends MediaRouterJellybean.Callback {
-        public void onRoutePresentationDisplayChanged(Object routeObj);
-    }
-
-    /**
-     * Workaround the fact that the version of MediaRouter.addCallback() that accepts a
-     * flag to perform an active scan does not exist in JB MR1 so we need to force
-     * wifi display scans directly through the DisplayManager.
-     * Do not use on JB MR2 and above.
-     */
-    public static final class ActiveScanWorkaround implements Runnable {
-        // Time between wifi display scans when actively scanning in milliseconds.
-        private static final int WIFI_DISPLAY_SCAN_INTERVAL = 15000;
-
-        private final DisplayManager mDisplayManager;
-        private final Handler mHandler;
-        private Method mScanWifiDisplaysMethod;
-
-        private boolean mActivelyScanningWifiDisplays;
-
-        public ActiveScanWorkaround(Context context, Handler handler) {
-            if (Build.VERSION.SDK_INT != 17) {
-                throw new UnsupportedOperationException();
-            }
-
-            mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
-            mHandler = handler;
-            try {
-                mScanWifiDisplaysMethod = DisplayManager.class.getMethod("scanWifiDisplays");
-            } catch (NoSuchMethodException ex) {
-            }
-        }
-
-        public void setActiveScanRouteTypes(int routeTypes) {
-            // On JB MR1, there is no API to scan wifi display routes.
-            // Instead we must make a direct call into the DisplayManager to scan
-            // wifi displays on this version but only when live video routes are requested.
-            // See also the JellybeanMr2Impl implementation of this method.
-            // This was fixed in JB MR2 by adding a new overload of addCallback() to
-            // enable active scanning on request.
-            if ((routeTypes & MediaRouterJellybean.ROUTE_TYPE_LIVE_VIDEO) != 0) {
-                if (!mActivelyScanningWifiDisplays) {
-                    if (mScanWifiDisplaysMethod != null) {
-                        mActivelyScanningWifiDisplays = true;
-                        mHandler.post(this);
-                    } else {
-                        Log.w(TAG, "Cannot scan for wifi displays because the "
-                                + "DisplayManager.scanWifiDisplays() method is "
-                                + "not available on this device.");
-                    }
-                }
-            } else {
-                if (mActivelyScanningWifiDisplays) {
-                    mActivelyScanningWifiDisplays = false;
-                    mHandler.removeCallbacks(this);
-                }
-            }
-        }
-
-        @Override
-        public void run() {
-            if (mActivelyScanningWifiDisplays) {
-                try {
-                    mScanWifiDisplaysMethod.invoke(mDisplayManager);
-                } catch (IllegalAccessException ex) {
-                    Log.w(TAG, "Cannot scan for wifi displays.", ex);
-                } catch (InvocationTargetException ex) {
-                    Log.w(TAG, "Cannot scan for wifi displays.", ex);
-                }
-                mHandler.postDelayed(this, WIFI_DISPLAY_SCAN_INTERVAL);
-            }
-        }
-    }
-
-    /**
-     * Workaround the fact that the isConnecting() method does not exist in JB MR1.
-     * Do not use on JB MR2 and above.
-     */
-    public static final class IsConnectingWorkaround {
-        private Method mGetStatusCodeMethod;
-        private int mStatusConnecting;
-
-        public IsConnectingWorkaround() {
-            if (Build.VERSION.SDK_INT != 17) {
-                throw new UnsupportedOperationException();
-            }
-
-            try {
-                Field statusConnectingField =
-                        android.media.MediaRouter.RouteInfo.class.getField("STATUS_CONNECTING");
-                mStatusConnecting = statusConnectingField.getInt(null);
-                mGetStatusCodeMethod =
-                        android.media.MediaRouter.RouteInfo.class.getMethod("getStatusCode");
-            } catch (NoSuchFieldException ex) {
-            } catch (NoSuchMethodException ex) {
-            } catch (IllegalAccessException ex) {
-            }
-        }
-
-        public boolean isConnecting(Object routeObj) {
-            android.media.MediaRouter.RouteInfo route =
-                    (android.media.MediaRouter.RouteInfo)routeObj;
-
-            if (mGetStatusCodeMethod != null) {
-                try {
-                    int statusCode = (Integer)mGetStatusCodeMethod.invoke(route);
-                    return statusCode == mStatusConnecting;
-                } catch (IllegalAccessException ex) {
-                } catch (InvocationTargetException ex) {
-                }
-            }
-
-            // Assume not connecting.
-            return false;
-        }
-    }
-
-    static class CallbackProxy<T extends Callback>
-            extends MediaRouterJellybean.CallbackProxy<T> {
-        public CallbackProxy(T callback) {
-            super(callback);
-        }
-
-        @Override
-        public void onRoutePresentationDisplayChanged(android.media.MediaRouter router,
-                android.media.MediaRouter.RouteInfo route) {
-            mCallback.onRoutePresentationDisplayChanged(route);
-        }
-    }
-}
diff --git a/v4/jellybean-mr2/android/support/v4/media/routing/MediaRouterJellybeanMr2.java b/v4/jellybean-mr2/android/support/v4/media/routing/MediaRouterJellybeanMr2.java
deleted file mode 100644
index 92a1607..0000000
--- a/v4/jellybean-mr2/android/support/v4/media/routing/MediaRouterJellybeanMr2.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v4.media.routing;
-
-class MediaRouterJellybeanMr2 extends MediaRouterJellybeanMr1 {
-    public static Object getDefaultRoute(Object routerObj) {
-        return ((android.media.MediaRouter)routerObj).getDefaultRoute();
-    }
-
-    public static void addCallback(Object routerObj, int types, Object callbackObj, int flags) {
-        ((android.media.MediaRouter)routerObj).addCallback(types,
-                (android.media.MediaRouter.Callback)callbackObj, flags);
-    }
-
-    public static final class RouteInfo {
-        public static CharSequence getDescription(Object routeObj) {
-            return ((android.media.MediaRouter.RouteInfo)routeObj).getDescription();
-        }
-
-        public static boolean isConnecting(Object routeObj) {
-            return ((android.media.MediaRouter.RouteInfo)routeObj).isConnecting();
-        }
-    }
-
-    public static final class UserRouteInfo {
-        public static void setDescription(Object routeObj, CharSequence description) {
-            ((android.media.MediaRouter.UserRouteInfo)routeObj).setDescription(description);
-        }
-    }
-}
diff --git a/v4/jellybean-mr2/android/support/v4/widget/TextViewCompatJbMr2.java b/v4/jellybean-mr2/android/support/v4/widget/TextViewCompatJbMr2.java
index ec9fe61..73f9666 100644
--- a/v4/jellybean-mr2/android/support/v4/widget/TextViewCompatJbMr2.java
+++ b/v4/jellybean-mr2/android/support/v4/widget/TextViewCompatJbMr2.java
@@ -17,6 +17,7 @@
 package android.support.v4.widget;
 
 import android.graphics.drawable.Drawable;
+import android.support.annotation.DrawableRes;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.widget.TextView;
@@ -36,7 +37,8 @@
     }
 
     public static void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
-            int start, int top, int end, int bottom) {
+            @DrawableRes int start, @DrawableRes int top, @DrawableRes int end,
+            @DrawableRes int bottom) {
         textView.setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom);
     }
 
diff --git a/v4/jellybean/android/support/v4/media/routing/MediaRouterJellybean.java b/v4/jellybean/android/support/v4/media/routing/MediaRouterJellybean.java
deleted file mode 100644
index 3cf6727..0000000
--- a/v4/jellybean/android/support/v4/media/routing/MediaRouterJellybean.java
+++ /dev/null
@@ -1,442 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v4.media.routing;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.util.Log;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.List;
-
-class MediaRouterJellybean {
-    private static final String TAG = "MediaRouterJellybean";
-
-    public static final int ROUTE_TYPE_LIVE_AUDIO = 0x1;
-    public static final int ROUTE_TYPE_LIVE_VIDEO = 0x2;
-    public static final int ROUTE_TYPE_USER = 0x00800000;
-
-    public static final int ALL_ROUTE_TYPES =
-            MediaRouterJellybean.ROUTE_TYPE_LIVE_AUDIO
-            | MediaRouterJellybean.ROUTE_TYPE_LIVE_VIDEO
-            | MediaRouterJellybean.ROUTE_TYPE_USER;
-
-    public static Object getMediaRouter(Context context) {
-        return context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
-    }
-
-    @SuppressWarnings({ "rawtypes", "unchecked" })
-    public static List getRoutes(Object routerObj) {
-        final android.media.MediaRouter router = (android.media.MediaRouter)routerObj;
-        final int count = router.getRouteCount();
-        List out = new ArrayList(count);
-        for (int i = 0; i < count; i++) {
-            out.add(router.getRouteAt(i));
-        }
-        return out;
-    }
-
-    @SuppressWarnings({ "rawtypes", "unchecked" })
-    public static List getCategories(Object routerObj) {
-        final android.media.MediaRouter router = (android.media.MediaRouter)routerObj;
-        final int count = router.getCategoryCount();
-        List out = new ArrayList(count);
-        for (int i = 0; i < count; i++) {
-            out.add(router.getCategoryAt(i));
-        }
-        return out;
-    }
-
-    public static Object getSelectedRoute(Object routerObj, int type) {
-        return ((android.media.MediaRouter)routerObj).getSelectedRoute(type);
-    }
-
-    public static void selectRoute(Object routerObj, int types, Object routeObj) {
-        ((android.media.MediaRouter)routerObj).selectRoute(types,
-                (android.media.MediaRouter.RouteInfo)routeObj);
-    }
-
-    public static void addCallback(Object routerObj, int types, Object callbackObj) {
-        ((android.media.MediaRouter)routerObj).addCallback(types,
-                (android.media.MediaRouter.Callback)callbackObj);
-    }
-
-    public static void removeCallback(Object routerObj, Object callbackObj) {
-        ((android.media.MediaRouter)routerObj).removeCallback(
-                (android.media.MediaRouter.Callback)callbackObj);
-    }
-
-    public static Object createRouteCategory(Object routerObj,
-            String name, boolean isGroupable) {
-        return ((android.media.MediaRouter)routerObj).createRouteCategory(name, isGroupable);
-    }
-
-    public static Object createUserRoute(Object routerObj, Object categoryObj) {
-        return ((android.media.MediaRouter)routerObj).createUserRoute(
-                (android.media.MediaRouter.RouteCategory)categoryObj);
-    }
-
-    public static void addUserRoute(Object routerObj, Object routeObj) {
-        ((android.media.MediaRouter)routerObj).addUserRoute(
-                (android.media.MediaRouter.UserRouteInfo)routeObj);
-    }
-
-    public static void removeUserRoute(Object routerObj, Object routeObj) {
-        ((android.media.MediaRouter)routerObj).removeUserRoute(
-                (android.media.MediaRouter.UserRouteInfo)routeObj);
-    }
-
-    public static Object createCallback(Callback callback) {
-        return new CallbackProxy<Callback>(callback);
-    }
-
-    public static Object createVolumeCallback(VolumeCallback callback) {
-        return new VolumeCallbackProxy<VolumeCallback>(callback);
-    }
-
-    public static final class RouteInfo {
-        public static CharSequence getName(Object routeObj, Context context) {
-            return ((android.media.MediaRouter.RouteInfo)routeObj).getName(context);
-        }
-
-        public static CharSequence getStatus(Object routeObj) {
-            return ((android.media.MediaRouter.RouteInfo)routeObj).getStatus();
-        }
-
-        public static int getSupportedTypes(Object routeObj) {
-            return ((android.media.MediaRouter.RouteInfo)routeObj).getSupportedTypes();
-        }
-
-        public static Object getCategory(Object routeObj) {
-            return ((android.media.MediaRouter.RouteInfo)routeObj).getCategory();
-        }
-
-        public static Drawable getIconDrawable(Object routeObj) {
-            return ((android.media.MediaRouter.RouteInfo)routeObj).getIconDrawable();
-        }
-
-        public static int getPlaybackType(Object routeObj) {
-            return ((android.media.MediaRouter.RouteInfo)routeObj).getPlaybackType();
-        }
-
-        public static int getPlaybackStream(Object routeObj) {
-            return ((android.media.MediaRouter.RouteInfo)routeObj).getPlaybackStream();
-        }
-
-        public static int getVolume(Object routeObj) {
-            return ((android.media.MediaRouter.RouteInfo)routeObj).getVolume();
-        }
-
-        public static int getVolumeMax(Object routeObj) {
-            return ((android.media.MediaRouter.RouteInfo)routeObj).getVolumeMax();
-        }
-
-        public static int getVolumeHandling(Object routeObj) {
-            return ((android.media.MediaRouter.RouteInfo)routeObj).getVolumeHandling();
-        }
-
-        public static Object getTag(Object routeObj) {
-            return ((android.media.MediaRouter.RouteInfo)routeObj).getTag();
-        }
-
-        public static void setTag(Object routeObj, Object tag) {
-            ((android.media.MediaRouter.RouteInfo)routeObj).setTag(tag);
-        }
-
-        public static void requestSetVolume(Object routeObj, int volume) {
-            ((android.media.MediaRouter.RouteInfo)routeObj).requestSetVolume(volume);
-        }
-
-        public static void requestUpdateVolume(Object routeObj, int direction) {
-            ((android.media.MediaRouter.RouteInfo)routeObj).requestUpdateVolume(direction);
-        }
-
-        public static Object getGroup(Object routeObj) {
-            return ((android.media.MediaRouter.RouteInfo)routeObj).getGroup();
-        }
-
-        public static boolean isGroup(Object routeObj) {
-            return routeObj instanceof android.media.MediaRouter.RouteGroup;
-        }
-    }
-
-    public static final class RouteGroup {
-        @SuppressWarnings({ "rawtypes", "unchecked" })
-        public static List getGroupedRoutes(Object groupObj) {
-            final android.media.MediaRouter.RouteGroup group =
-                    (android.media.MediaRouter.RouteGroup)groupObj;
-            final int count = group.getRouteCount();
-            List out = new ArrayList(count);
-            for (int i = 0; i < count; i++) {
-                out.add(group.getRouteAt(i));
-            }
-            return out;
-        }
-    }
-
-    public static final class UserRouteInfo {
-        public static void setName(Object routeObj, CharSequence name) {
-            ((android.media.MediaRouter.UserRouteInfo)routeObj).setName(name);
-        }
-
-        public static void setStatus(Object routeObj, CharSequence status) {
-            ((android.media.MediaRouter.UserRouteInfo)routeObj).setStatus(status);
-        }
-
-        public static void setIconDrawable(Object routeObj, Drawable icon) {
-            ((android.media.MediaRouter.UserRouteInfo)routeObj).setIconDrawable(icon);
-        }
-
-        public static void setPlaybackType(Object routeObj, int type) {
-            ((android.media.MediaRouter.UserRouteInfo)routeObj).setPlaybackType(type);
-        }
-
-        public static void setPlaybackStream(Object routeObj, int stream) {
-            ((android.media.MediaRouter.UserRouteInfo)routeObj).setPlaybackStream(stream);
-        }
-
-        public static void setVolume(Object routeObj, int volume) {
-            ((android.media.MediaRouter.UserRouteInfo)routeObj).setVolume(volume);
-        }
-
-        public static void setVolumeMax(Object routeObj, int volumeMax) {
-            ((android.media.MediaRouter.UserRouteInfo)routeObj).setVolumeMax(volumeMax);
-        }
-
-        public static void setVolumeHandling(Object routeObj, int volumeHandling) {
-            ((android.media.MediaRouter.UserRouteInfo)routeObj).setVolumeHandling(volumeHandling);
-        }
-
-        public static void setVolumeCallback(Object routeObj, Object volumeCallbackObj) {
-            ((android.media.MediaRouter.UserRouteInfo)routeObj).setVolumeCallback(
-                    (android.media.MediaRouter.VolumeCallback)volumeCallbackObj);
-        }
-
-        public static void setRemoteControlClient(Object routeObj, Object rccObj) {
-            ((android.media.MediaRouter.UserRouteInfo)routeObj).setRemoteControlClient(
-                    (android.media.RemoteControlClient)rccObj);
-        }
-    }
-
-    public static final class RouteCategory {
-        public static CharSequence getName(Object categoryObj, Context context) {
-            return ((android.media.MediaRouter.RouteCategory)categoryObj).getName(context);
-        }
-
-        @SuppressWarnings({ "rawtypes", "unchecked" })
-        public static List getRoutes(Object categoryObj) {
-            ArrayList out = new ArrayList();
-            ((android.media.MediaRouter.RouteCategory)categoryObj).getRoutes(out);
-            return out;
-        }
-
-        public static int getSupportedTypes(Object categoryObj) {
-            return ((android.media.MediaRouter.RouteCategory)categoryObj).getSupportedTypes();
-        }
-
-        public static boolean isGroupable(Object categoryObj) {
-            return ((android.media.MediaRouter.RouteCategory)categoryObj).isGroupable();
-        }
-    }
-
-    public static interface Callback {
-        public void onRouteSelected(int type, Object routeObj);
-        public void onRouteUnselected(int type, Object routeObj);
-        public void onRouteAdded(Object routeObj);
-        public void onRouteRemoved(Object routeObj);
-        public void onRouteChanged(Object routeObj);
-        public void onRouteGrouped(Object routeObj, Object groupObj, int index);
-        public void onRouteUngrouped(Object routeObj, Object groupObj);
-        public void onRouteVolumeChanged(Object routeObj);
-    }
-
-    public static interface VolumeCallback {
-        public void onVolumeSetRequest(Object routeObj, int volume);
-        public void onVolumeUpdateRequest(Object routeObj, int direction);
-    }
-
-    /**
-     * Workaround for limitations of selectRoute() on JB and JB MR1.
-     * Do not use on JB MR2 and above.
-     */
-    public static final class SelectRouteWorkaround {
-        private Method mSelectRouteIntMethod;
-
-        public SelectRouteWorkaround() {
-            if (Build.VERSION.SDK_INT < 16 || Build.VERSION.SDK_INT > 17) {
-                throw new UnsupportedOperationException();
-            }
-            try {
-                mSelectRouteIntMethod = android.media.MediaRouter.class.getMethod(
-                        "selectRouteInt", int.class, android.media.MediaRouter.RouteInfo.class);
-            } catch (NoSuchMethodException ex) {
-            }
-        }
-
-        public void selectRoute(Object routerObj, int types, Object routeObj) {
-            android.media.MediaRouter router = (android.media.MediaRouter)routerObj;
-            android.media.MediaRouter.RouteInfo route =
-                    (android.media.MediaRouter.RouteInfo)routeObj;
-
-            int routeTypes = route.getSupportedTypes();
-            if ((routeTypes & ROUTE_TYPE_USER) == 0) {
-                // Handle non-user routes.
-                // On JB and JB MR1, the selectRoute() API only supports programmatically
-                // selecting user routes.  So instead we rely on the hidden selectRouteInt()
-                // method on these versions of the platform.
-                // This limitation was removed in JB MR2.
-                if (mSelectRouteIntMethod != null) {
-                    try {
-                        mSelectRouteIntMethod.invoke(router, types, route);
-                        return; // success!
-                    } catch (IllegalAccessException ex) {
-                        Log.w(TAG, "Cannot programmatically select non-user route.  "
-                                + "Media routing may not work.", ex);
-                    } catch (InvocationTargetException ex) {
-                        Log.w(TAG, "Cannot programmatically select non-user route.  "
-                                + "Media routing may not work.", ex);
-                    }
-                } else {
-                    Log.w(TAG, "Cannot programmatically select non-user route "
-                            + "because the platform is missing the selectRouteInt() "
-                            + "method.  Media routing may not work.");
-                }
-            }
-
-            // Default handling.
-            router.selectRoute(types, route);
-        }
-    }
-
-    /**
-     * Workaround the fact that the getDefaultRoute() method does not exist in JB and JB MR1.
-     * Do not use on JB MR2 and above.
-     */
-    public static final class GetDefaultRouteWorkaround {
-        private Method mGetSystemAudioRouteMethod;
-
-        public GetDefaultRouteWorkaround() {
-            if (Build.VERSION.SDK_INT < 16 || Build.VERSION.SDK_INT > 17) {
-                throw new UnsupportedOperationException();
-            }
-            try {
-                mGetSystemAudioRouteMethod =
-                        android.media.MediaRouter.class.getMethod("getSystemAudioRoute");
-            } catch (NoSuchMethodException ex) {
-            }
-        }
-
-        public Object getDefaultRoute(Object routerObj) {
-            android.media.MediaRouter router = (android.media.MediaRouter)routerObj;
-
-            if (mGetSystemAudioRouteMethod != null) {
-                try {
-                    return mGetSystemAudioRouteMethod.invoke(router);
-                } catch (IllegalAccessException ex) {
-                } catch (InvocationTargetException ex) {
-                }
-            }
-
-            // Could not find the method or it does not work.
-            // Return the first route and hope for the best.
-            return router.getRouteAt(0);
-        }
-    }
-
-    static class CallbackProxy<T extends Callback>
-            extends android.media.MediaRouter.Callback {
-        protected final T mCallback;
-
-        public CallbackProxy(T callback) {
-            mCallback = callback;
-        }
-
-        @Override
-        public void onRouteSelected(android.media.MediaRouter router,
-                int type, android.media.MediaRouter.RouteInfo route) {
-            mCallback.onRouteSelected(type, route);
-        }
-
-        @Override
-        public void onRouteUnselected(android.media.MediaRouter router,
-                int type, android.media.MediaRouter.RouteInfo route) {
-            mCallback.onRouteUnselected(type, route);
-        }
-
-        @Override
-        public void onRouteAdded(android.media.MediaRouter router,
-                android.media.MediaRouter.RouteInfo route) {
-            mCallback.onRouteAdded(route);
-        }
-
-        @Override
-        public void onRouteRemoved(android.media.MediaRouter router,
-                android.media.MediaRouter.RouteInfo route) {
-            mCallback.onRouteRemoved(route);
-        }
-
-        @Override
-        public void onRouteChanged(android.media.MediaRouter router,
-                android.media.MediaRouter.RouteInfo route) {
-            mCallback.onRouteChanged(route);
-        }
-
-        @Override
-        public void onRouteGrouped(android.media.MediaRouter router,
-                android.media.MediaRouter.RouteInfo route,
-                android.media.MediaRouter.RouteGroup group, int index) {
-            mCallback.onRouteGrouped(route, group, index);
-        }
-
-        @Override
-        public void onRouteUngrouped(android.media.MediaRouter router,
-                android.media.MediaRouter.RouteInfo route,
-                android.media.MediaRouter.RouteGroup group) {
-            mCallback.onRouteUngrouped(route, group);
-        }
-
-        @Override
-        public void onRouteVolumeChanged(android.media.MediaRouter router,
-                android.media.MediaRouter.RouteInfo route) {
-            mCallback.onRouteVolumeChanged(route);
-        }
-    }
-
-    static class VolumeCallbackProxy<T extends VolumeCallback>
-            extends android.media.MediaRouter.VolumeCallback {
-        protected final T mCallback;
-
-        public VolumeCallbackProxy(T callback) {
-            mCallback = callback;
-        }
-
-        @Override
-        public void onVolumeSetRequest(android.media.MediaRouter.RouteInfo route,
-                int volume) {
-            mCallback.onVolumeSetRequest(route, volume);
-        }
-
-        @Override
-        public void onVolumeUpdateRequest(android.media.MediaRouter.RouteInfo route,
-                int direction) {
-            mCallback.onVolumeUpdateRequest(route, direction);
-        }
-    }
-}
diff --git a/v4/kitkat/android/support/v4/graphics/drawable/DrawableWrapperKitKat.java b/v4/kitkat/android/support/v4/graphics/drawable/DrawableWrapperKitKat.java
index dc70c6e..67a5a8c 100644
--- a/v4/kitkat/android/support/v4/graphics/drawable/DrawableWrapperKitKat.java
+++ b/v4/kitkat/android/support/v4/graphics/drawable/DrawableWrapperKitKat.java
@@ -16,7 +16,9 @@
 
 package android.support.v4.graphics.drawable;
 
+import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
+import android.support.annotation.Nullable;
 
 class DrawableWrapperKitKat extends DrawableWrapperHoneycomb {
 
@@ -24,6 +26,10 @@
         super(drawable);
     }
 
+    DrawableWrapperKitKat(DrawableWrapperState state, Resources resources) {
+        super(state, resources);
+    }
+
     @Override
     public void setAutoMirrored(boolean mirrored) {
         mDrawable.setAutoMirrored(mirrored);
@@ -33,4 +39,21 @@
     public boolean isAutoMirrored() {
         return mDrawable.isAutoMirrored();
     }
+
+    @Override
+    DrawableWrapperState mutateConstantState() {
+        return new DrawableWrapperStateKitKat(mState, null);
+    }
+
+    private static class DrawableWrapperStateKitKat extends DrawableWrapperState {
+        DrawableWrapperStateKitKat(@Nullable DrawableWrapperState orig,
+                @Nullable Resources res) {
+            super(orig, res);
+        }
+
+        @Override
+        public Drawable newDrawable(@Nullable Resources res) {
+            return new DrawableWrapperKitKat(this, res);
+        }
+    }
 }
diff --git a/v4/tests/AndroidManifest.xml b/v4/tests/AndroidManifest.xml
index f99c5d1..85ffd7d 100644
--- a/v4/tests/AndroidManifest.xml
+++ b/v4/tests/AndroidManifest.xml
@@ -20,7 +20,8 @@
     <uses-sdk
             android:minSdkVersion="4"
             android:targetSdkVersion="23"
-            tools:overrideLibrary="android.support.test,android.support.test.espresso, android.support.test.espresso.idling"/>
+            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.VIBRATE"/>
     <uses-permission android:name="android.permission.WAKE_LOCK"/>
@@ -29,7 +30,7 @@
 
     <application android:supportsRtl="true">
         <uses-library android:name="android.test.runner" />
-        <activity android:name="android.support.v4.widget.test.TextViewTestActivity"/>
+        <activity android:name="android.support.v4.widget.TextViewTestActivity"/>
 
         <activity android:name="android.support.v4.view.ViewPagerWithTitleStripActivity"/>
 
diff --git a/v4/tests/java/android/support/v4/BaseInstrumentationTestCase.java b/v4/tests/java/android/support/v4/BaseInstrumentationTestCase.java
new file mode 100644
index 0000000..5f9ce85
--- /dev/null
+++ b/v4/tests/java/android/support/v4/BaseInstrumentationTestCase.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;
+
+import android.app.Activity;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public abstract class BaseInstrumentationTestCase<A extends Activity> {
+    @Rule
+    public final ActivityTestRule<A> mActivityTestRule;
+
+    protected BaseInstrumentationTestCase(Class<A> activityClass) {
+        mActivityTestRule = new ActivityTestRule<A>(activityClass);
+    }
+}
diff --git a/v4/tests/java/android/support/v4/BaseTestActivity.java b/v4/tests/java/android/support/v4/BaseTestActivity.java
new file mode 100755
index 0000000..e0682ce
--- /dev/null
+++ b/v4/tests/java/android/support/v4/BaseTestActivity.java
@@ -0,0 +1,39 @@
+/*
+ * 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;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+public abstract class BaseTestActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+        final int contentView = getContentViewLayoutResId();
+        if (contentView > 0) {
+            setContentView(contentView);
+        }
+        onContentViewSet();
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+    }
+
+    protected abstract int getContentViewLayoutResId();
+
+    protected void onContentViewSet() {}
+}
diff --git a/v4/tests/java/android/support/v4/testutils/LayoutDirectionActions.java b/v4/tests/java/android/support/v4/testutils/LayoutDirectionActions.java
new file mode 100755
index 0000000..25ae971
--- /dev/null
+++ b/v4/tests/java/android/support/v4/testutils/LayoutDirectionActions.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.testutils;
+
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.support.v4.view.ViewCompat;
+import android.view.View;
+import org.hamcrest.Matcher;
+
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+
+public class LayoutDirectionActions {
+    /**
+     * 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();
+            }
+        };
+    }
+}
diff --git a/v4/tests/java/android/support/v4/testutils/TextViewActions.java b/v4/tests/java/android/support/v4/testutils/TextViewActions.java
new file mode 100644
index 0000000..f67e8c0
--- /dev/null
+++ b/v4/tests/java/android/support/v4/testutils/TextViewActions.java
@@ -0,0 +1,230 @@
+/*
+ * 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.testutils;
+
+import android.graphics.drawable.Drawable;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.Nullable;
+import android.support.annotation.StringRes;
+import android.support.annotation.StyleRes;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.support.v4.widget.TextViewCompat;
+import android.view.View;
+import android.widget.TextView;
+import org.hamcrest.Matcher;
+
+import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
+
+public class TextViewActions {
+    /**
+     * Sets max lines count on <code>TextView</code>.
+     */
+    public static ViewAction setMaxLines(final int maxLines) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isAssignableFrom(TextView.class);
+            }
+
+            @Override
+            public String getDescription() {
+                return "TextView set max lines";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                TextView textView = (TextView) view;
+                textView.setMaxLines(maxLines);
+
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
+
+    /**
+     * Sets min lines count on <code>TextView</code>.
+     */
+    public static ViewAction setMinLines(final int minLines) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isAssignableFrom(TextView.class);
+            }
+
+            @Override
+            public String getDescription() {
+                return "TextView set min lines";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                TextView textView = (TextView) view;
+                textView.setMinLines(minLines);
+
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
+
+    /**
+     * Sets text content on <code>TextView</code>.
+     */
+    public static ViewAction setText(final @StringRes int stringResId) {
+        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(stringResId);
+
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
+
+    /**
+     * Sets text appearance on <code>TextView</code>.
+     */
+    public static ViewAction setTextAppearance(final @StyleRes int styleResId) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return 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;
+                TextViewCompat.setTextAppearance(textView, styleResId);
+
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
+
+    /**
+     * Sets compound drawables on <code>TextView</code>.
+     */
+    public static ViewAction setCompoundDrawablesRelative(final @Nullable Drawable start,
+            final @Nullable Drawable top, final @Nullable Drawable end,
+            final @Nullable Drawable bottom) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isAssignableFrom(TextView.class);
+            }
+
+            @Override
+            public String getDescription() {
+                return "TextView set compound drawables";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                TextView textView = (TextView) view;
+                TextViewCompat.setCompoundDrawablesRelative(textView, start, top, end, bottom);
+
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
+
+    /**
+     * Sets compound drawables on <code>TextView</code>.
+     */
+    public static ViewAction setCompoundDrawablesRelativeWithIntrinsicBounds(
+            final @Nullable Drawable start, final @Nullable Drawable top,
+            final @Nullable Drawable end, final @Nullable Drawable bottom) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isAssignableFrom(TextView.class);
+            }
+
+            @Override
+            public String getDescription() {
+                return "TextView set compound drawables";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                TextView textView = (TextView) view;
+                TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(
+                        textView, start, top, end, bottom);
+
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
+
+    /**
+     * Sets compound drawables on <code>TextView</code>.
+     */
+    public static ViewAction setCompoundDrawablesRelativeWithIntrinsicBounds(
+            final @DrawableRes int start, final @DrawableRes int top, final @DrawableRes int end,
+            final @DrawableRes int bottom) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isAssignableFrom(TextView.class);
+            }
+
+            @Override
+            public String getDescription() {
+                return "TextView set compound drawables";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                TextView textView = (TextView) view;
+                TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(
+                        textView, start, top, end, bottom);
+
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
+}
diff --git a/v4/tests/java/android/support/v4/view/BaseViewPagerTest.java b/v4/tests/java/android/support/v4/view/BaseViewPagerTest.java
index 166fb1e..13d8372 100644
--- a/v4/tests/java/android/support/v4/view/BaseViewPagerTest.java
+++ b/v4/tests/java/android/support/v4/view/BaseViewPagerTest.java
@@ -17,47 +17,32 @@
 
 import android.app.Activity;
 import android.graphics.Color;
+import android.support.v4.BaseInstrumentationTestCase;
+import android.support.v4.test.R;
+import android.support.v4.testutils.TestUtilsAssertions;
+import android.support.v4.testutils.TestUtilsMatchers;
+import android.test.suitebuilder.annotation.SmallTest;
 import android.text.TextUtils;
 import android.util.Pair;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.TextView;
-
-import android.support.test.espresso.Espresso;
-import android.support.test.espresso.UiController;
-import android.support.v4.test.R;
-import android.support.v4.testutils.TestUtilsAssertions;
-import android.support.v4.testutils.TestUtilsMatchers;
-import android.support.v4.view.ViewPager;
-import android.support.v4.view.PagerTitleStrip;
-import android.support.v4.widget.TestActivity;
-
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.SmallTest;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
 
 import java.util.ArrayList;
 
-import org.hamcrest.Matcher;
-
 import static android.support.test.espresso.Espresso.onView;
-import static android.support.test.espresso.action.ViewActions.click;
 import static android.support.test.espresso.action.ViewActions.swipeLeft;
 import static android.support.test.espresso.action.ViewActions.swipeRight;
+import static android.support.test.espresso.assertion.PositionAssertions.*;
 import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
 import static android.support.test.espresso.assertion.ViewAssertions.matches;
-import static android.support.test.espresso.assertion.PositionAssertions.isBelow;
-import static android.support.test.espresso.assertion.PositionAssertions.isBottomAlignedWith;
-import static android.support.test.espresso.assertion.PositionAssertions.isLeftAlignedWith;
-import static android.support.test.espresso.assertion.PositionAssertions.isRightAlignedWith;
-import static android.support.test.espresso.assertion.PositionAssertions.isTopAlignedWith;
-import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant;
-import static android.support.test.espresso.matcher.ViewMatchers.isDescendantOfA;
-import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
-import static android.support.test.espresso.matcher.ViewMatchers.withId;
-import static android.support.test.espresso.matcher.ViewMatchers.withText;
-
+import static android.support.test.espresso.matcher.ViewMatchers.*;
 import static org.hamcrest.Matchers.allOf;
 import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertEquals;
 
 /**
  * Base class for testing <code>ViewPager</code>. Most of the testing logic should be in this
@@ -67,8 +52,7 @@
  * Testing logic that does depend on the specific pager title implementation is pushed into the
  * extending classes in <code>assertStripInteraction()</code> method.
  */
-public abstract class BaseViewPagerTest<T extends Activity>
-        extends ActivityInstrumentationTestCase2<T> {
+public abstract class BaseViewPagerTest<T extends Activity> extends BaseInstrumentationTestCase<T> {
     protected ViewPager mViewPager;
 
     protected static class BasePagerAdapter<Q> extends PagerAdapter {
@@ -181,14 +165,12 @@
     }
 
     public BaseViewPagerTest(Class<T> activityClass) {
-        super("android.support.v4.view", activityClass);
+        super(activityClass);
     }
 
-    @Override
+    @Before
     public void setUp() throws Exception {
-        super.setUp();
-
-        final T activity = getActivity();
+        final T activity = mActivityTestRule.getActivity();
         mViewPager = (ViewPager) activity.findViewById(R.id.pager);
 
         ColorPagerAdapter adapter = new ColorPagerAdapter();
@@ -199,13 +181,12 @@
                 ViewPagerActions.scrollToPage(0));
     }
 
-    @Override
+    @After
     public void tearDown() throws Exception {
         onView(withId(R.id.pager)).perform(ViewPagerActions.setAdapter(null));
-
-        super.tearDown();
     }
 
+    @Test
     @SmallTest
     public void testPageSelections() {
         assertEquals("Initial state", 0, mViewPager.getCurrentItem());
@@ -232,6 +213,7 @@
 
     }
 
+    @Test
     @SmallTest
     public void testPageSwipes() {
         assertEquals("Initial state", 0, mViewPager.getCurrentItem());
@@ -257,6 +239,7 @@
         assertEquals("Swipe right beyond first page", 0, mViewPager.getCurrentItem());
     }
 
+    @Test
     @SmallTest
     public void testPageSwipesComposite() {
         assertEquals("Initial state", 0, mViewPager.getCurrentItem());
@@ -275,6 +258,7 @@
         assertEquals("Swipe right beyond first page and then left", 1, mViewPager.getCurrentItem());
     }
 
+    @Test
     @SmallTest
     public void testPageContent() {
         assertEquals("Initial state", 0, mViewPager.getCurrentItem());
@@ -315,6 +299,7 @@
                 TestUtilsMatchers.backgroundColor(Color.BLUE))));
     }
 
+    @Test
     @SmallTest
     public void testAdapterChange() {
         // Verify that we have the expected initial adapter
@@ -427,6 +412,7 @@
         }
     }
 
+    @Test
     @SmallTest
     public void testPagerStrip() {
         // Set an adapter with 5 pages
diff --git a/v4/tests/java/android/support/v4/view/GravityCompatTest.java b/v4/tests/java/android/support/v4/view/GravityCompatTest.java
index db62b0c..2e2f180 100644
--- a/v4/tests/java/android/support/v4/view/GravityCompatTest.java
+++ b/v4/tests/java/android/support/v4/view/GravityCompatTest.java
@@ -17,15 +17,10 @@
 
 import android.graphics.Rect;
 import android.os.Build;
-import android.view.Gravity;
-import android.view.View;
-
 import android.support.v4.testutils.TestUtils;
-import android.support.v4.view.GravityCompat;
-import android.support.v4.view.ViewCompat;
-
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.view.Gravity;
 
 public class GravityCompatTest extends AndroidTestCase {
     @SmallTest
diff --git a/v4/tests/java/android/support/v4/view/MarginLayoutParamsCompatTest.java b/v4/tests/java/android/support/v4/view/MarginLayoutParamsCompatTest.java
index db08a1a..057db99 100644
--- a/v4/tests/java/android/support/v4/view/MarginLayoutParamsCompatTest.java
+++ b/v4/tests/java/android/support/v4/view/MarginLayoutParamsCompatTest.java
@@ -15,16 +15,10 @@
  */
 package android.support.v4.view;
 
-import android.view.View;
-
-import android.support.v4.view.MarginLayoutParamsCompat;
-import android.support.v4.view.ViewCompat;
-
 import android.os.Build;
-import android.view.ViewGroup;
-
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.view.ViewGroup;
 
 public class MarginLayoutParamsCompatTest extends AndroidTestCase {
     @SmallTest
diff --git a/v4/tests/java/android/support/v4/view/ViewCompatTest.java b/v4/tests/java/android/support/v4/view/ViewCompatTest.java
index 483d16b..29a0f5a 100644
--- a/v4/tests/java/android/support/v4/view/ViewCompatTest.java
+++ b/v4/tests/java/android/support/v4/view/ViewCompatTest.java
@@ -15,12 +15,9 @@
  */
 package android.support.v4.view;
 
-import android.view.View;
-
-import android.support.v4.view.ViewCompat;
-
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.view.View;
 
 public class ViewCompatTest extends AndroidTestCase {
     @SmallTest
diff --git a/v4/tests/java/android/support/v4/view/ViewPagerActions.java b/v4/tests/java/android/support/v4/view/ViewPagerActions.java
index b1e1a76..2b37e6f 100644
--- a/v4/tests/java/android/support/v4/view/ViewPagerActions.java
+++ b/v4/tests/java/android/support/v4/view/ViewPagerActions.java
@@ -22,20 +22,12 @@
 import android.support.test.espresso.action.GeneralClickAction;
 import android.support.test.espresso.action.Press;
 import android.support.test.espresso.action.Tap;
-import android.support.v4.view.PagerAdapter;
-import android.support.v4.view.ViewPager;
 import android.view.View;
 import android.widget.TextView;
-
 import org.hamcrest.Matcher;
 
-import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant;
 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 android.support.test.espresso.matcher.ViewMatchers.withText;
-
-import static org.hamcrest.Matchers.allOf;
 
 public class ViewPagerActions {
     /**
diff --git a/v4/tests/java/android/support/v4/view/ViewPagerWithTabStripActivity.java b/v4/tests/java/android/support/v4/view/ViewPagerWithTabStripActivity.java
index edc4da9..2cb3048 100644
--- a/v4/tests/java/android/support/v4/view/ViewPagerWithTabStripActivity.java
+++ b/v4/tests/java/android/support/v4/view/ViewPagerWithTabStripActivity.java
@@ -21,8 +21,6 @@
 import android.support.v4.test.R;
 import android.view.WindowManager;
 
-import java.util.ArrayList;
-
 public class ViewPagerWithTabStripActivity extends Activity {
     @Override
     protected void onCreate(Bundle savedInstanceState) {
diff --git a/v4/tests/java/android/support/v4/view/ViewPagerWithTabStripTest.java b/v4/tests/java/android/support/v4/view/ViewPagerWithTabStripTest.java
index 706d2e2..3e4a82f 100644
--- a/v4/tests/java/android/support/v4/view/ViewPagerWithTabStripTest.java
+++ b/v4/tests/java/android/support/v4/view/ViewPagerWithTabStripTest.java
@@ -15,7 +15,6 @@
  */
 package android.support.v4.view;
 
-import android.support.v4.view.PagerTitleStrip;
 import android.support.v4.test.R;
 
 import static android.support.test.espresso.Espresso.onView;
@@ -23,8 +22,8 @@
 import static android.support.test.espresso.matcher.ViewMatchers.isDescendantOfA;
 import static android.support.test.espresso.matcher.ViewMatchers.withId;
 import static android.support.test.espresso.matcher.ViewMatchers.withText;
-
 import static org.hamcrest.Matchers.allOf;
+import static org.junit.Assert.assertEquals;
 
 /**
  * Provides assertions that depend on the interactive nature of <code>PagerTabStrip</code>.
diff --git a/v4/tests/java/android/support/v4/view/ViewPagerWithTitleStripActivity.java b/v4/tests/java/android/support/v4/view/ViewPagerWithTitleStripActivity.java
index 28dcb07..49d6e76 100644
--- a/v4/tests/java/android/support/v4/view/ViewPagerWithTitleStripActivity.java
+++ b/v4/tests/java/android/support/v4/view/ViewPagerWithTitleStripActivity.java
@@ -21,8 +21,6 @@
 import android.support.v4.test.R;
 import android.view.WindowManager;
 
-import java.util.ArrayList;
-
 public class ViewPagerWithTitleStripActivity extends Activity {
     @Override
     protected void onCreate(Bundle savedInstanceState) {
diff --git a/v4/tests/java/android/support/v4/view/ViewPagerWithTitleStripTest.java b/v4/tests/java/android/support/v4/view/ViewPagerWithTitleStripTest.java
index 528db86..8d831dc 100644
--- a/v4/tests/java/android/support/v4/view/ViewPagerWithTitleStripTest.java
+++ b/v4/tests/java/android/support/v4/view/ViewPagerWithTitleStripTest.java
@@ -15,7 +15,6 @@
  */
 package android.support.v4.view;
 
-import android.support.v4.view.PagerTitleStrip;
 import android.support.v4.test.R;
 
 import static android.support.test.espresso.Espresso.onView;
@@ -23,8 +22,8 @@
 import static android.support.test.espresso.matcher.ViewMatchers.isDescendantOfA;
 import static android.support.test.espresso.matcher.ViewMatchers.withId;
 import static android.support.test.espresso.matcher.ViewMatchers.withText;
-
 import static org.hamcrest.Matchers.allOf;
+import static org.junit.Assert.assertEquals;
 
 /**
  * Provides assertions that depend on the non-interactive nature of <code>PagerTabStrip</code>.
diff --git a/v4/tests/java/android/support/v4/widget/TextViewCompatTest.java b/v4/tests/java/android/support/v4/widget/TextViewCompatTest.java
index d0f5b00..3ef236f 100644
--- a/v4/tests/java/android/support/v4/widget/TextViewCompatTest.java
+++ b/v4/tests/java/android/support/v4/widget/TextViewCompatTest.java
@@ -17,35 +17,27 @@
 
 package android.support.v4.widget;
 
-import org.junit.After;
+import android.content.res.Resources;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.ColorInt;
+import android.support.v4.BaseInstrumentationTestCase;
+import android.support.v4.test.R;
+import android.support.v4.testutils.TestUtils;
+import android.support.v4.view.ViewCompat;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.widget.TextView;
 import org.junit.Before;
 import org.junit.Test;
-import org.junit.runner.RunWith;
 
-import android.app.Instrumentation;
-import android.content.res.Resources;
-import android.graphics.Typeface;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.ColorDrawable;
-import android.os.Looper;
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.UiThreadTest;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.support.annotation.ColorInt;
-import android.support.annotation.LayoutRes;
-import android.support.test.InstrumentationRegistry;
-import android.support.v4.test.R;
-import android.support.v4.view.ViewCompat;
-import android.support.v4.widget.TextViewCompat;
-import android.support.v4.testutils.TestUtils;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.TextView;
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static android.support.v4.testutils.LayoutDirectionActions.setLayoutDirection;
+import static android.support.v4.testutils.TextViewActions.*;
+import static org.junit.Assert.*;
 
-public class TextViewCompatTest extends ActivityInstrumentationTestCase2<TestActivity> {
+public class TextViewCompatTest extends BaseInstrumentationTestCase<TextViewTestActivity> {
     private static final String TAG = "TextViewCompatTest";
 
     private TextView mTextView;
@@ -72,127 +64,75 @@
     }
 
     public TextViewCompatTest() {
-        super("android.support.v4.widget", TestActivity.class);
+        super(TextViewTestActivity.class);
     }
 
-    @Override
-    public void tearDown() throws Exception {
-        if (mTextView != null) {
-            removeTextView();
-        }
-
-        getInstrumentation().waitForIdleSync();
-        super.tearDown();
+    @Before
+    public void setUp() {
+        mTextView = (TextView) mActivityTestRule.getActivity().findViewById(R.id.text_view);
     }
 
-    private boolean isMainThread() {
-        return Looper.myLooper() == Looper.getMainLooper();
-    }
-
-    private void removeTextView() {
-        if (mTextView == null) {
-            return;
-        }
-        if (!isMainThread()) {
-            getInstrumentation().waitForIdleSync();
-        }
-        try {
-            runTestOnUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    getActivity().mContainer.removeAllViews();
-                }
-            });
-        } catch (Throwable throwable) {
-            Log.e(TAG, "", throwable);
-        }
-        mTextView = null;
-    }
-
-    private void createAndAddTextView() {
-        final TestActivity activity = getActivity();
-        mTextView = new TextView(activity);
-        activity.mContainer.addView(mTextView);
-
-        // Explicitly measure and layout the text view. This way the core TextView updates its
-        // internal tracking of various visual facets so those can be tested in the relevant
-        // tests - such as, for example, where each drawable is positioned relative to the text.
-        final DisplayMetrics metrics = getActivity().getResources().getDisplayMetrics();
-        int textViewWidthPx = TestUtils.convertSizeDipsToPixels(metrics, 200);
-        int textViewHeightPx = TestUtils.convertSizeDipsToPixels(metrics, 60);
-        mTextView.measure(
-                View.MeasureSpec.makeMeasureSpec(textViewWidthPx, View.MeasureSpec.EXACTLY),
-                View.MeasureSpec.makeMeasureSpec(textViewHeightPx, View.MeasureSpec.EXACTLY));
-        mTextView.layout(0, 0, textViewWidthPx, textViewHeightPx);
-    }
-
-    @UiThreadTest
+    @Test
     @SmallTest
     public void testMaxLines() throws Throwable {
-        createAndAddTextView();
         final int maxLinesCount = 4;
-        mTextView.setMaxLines(maxLinesCount);
+        onView(withId(R.id.text_view)).perform(setMaxLines(maxLinesCount));
 
         assertEquals("Empty view: Max lines must match", TextViewCompat.getMaxLines(mTextView),
                 maxLinesCount);
 
-        mTextView.setText(R.string.test_text_short);
+        onView(withId(R.id.text_view)).perform(setText(R.string.test_text_short));
         assertEquals("Short text: Max lines must match", TextViewCompat.getMaxLines(mTextView),
                 maxLinesCount);
 
-        mTextView.setText(R.string.test_text_medium);
+        onView(withId(R.id.text_view)).perform(setText(R.string.test_text_medium));
         assertEquals("Medium text: Max lines must match", TextViewCompat.getMaxLines(mTextView),
                 maxLinesCount);
 
-        mTextView.setText(R.string.test_text_long);
+        onView(withId(R.id.text_view)).perform(setText(R.string.test_text_long));
         assertEquals("Long text: Max lines must match", TextViewCompat.getMaxLines(mTextView),
                 maxLinesCount);
     }
 
-    @UiThreadTest
+    @Test
     @SmallTest
     public void testMinLines() throws Throwable {
-        createAndAddTextView();
         final int minLinesCount = 3;
-        mTextView.setMinLines(minLinesCount);
+        onView(withId(R.id.text_view)).perform(setMinLines(minLinesCount));
 
         assertEquals("Empty view: Min lines must match", TextViewCompat.getMinLines(mTextView),
                 minLinesCount);
 
-        mTextView.setText(R.string.test_text_short);
+        onView(withId(R.id.text_view)).perform(setText(R.string.test_text_short));
         assertEquals("Short text: Min lines must match", TextViewCompat.getMinLines(mTextView),
                 minLinesCount);
 
-        mTextView.setText(R.string.test_text_medium);
+        onView(withId(R.id.text_view)).perform(setText(R.string.test_text_medium));
         assertEquals("Medium text: Min lines must match", TextViewCompat.getMinLines(mTextView),
                 minLinesCount);
 
-        mTextView.setText(R.string.test_text_long);
+        onView(withId(R.id.text_view)).perform(setText(R.string.test_text_long));
         assertEquals("Long text: Min lines must match", TextViewCompat.getMinLines(mTextView),
                 minLinesCount);
     }
 
-    @UiThreadTest
+    @Test
     @SmallTest
     public void testStyle() throws Throwable {
-        createAndAddTextView();
+        onView(withId(R.id.text_view)).perform(setTextAppearance(R.style.TextMediumStyle));
 
-        TextViewCompat.setTextAppearance(mTextView, R.style.TextMediumStyle);
-
-        final Resources res = getActivity().getResources();
+        final Resources res = mActivityTestRule.getActivity().getResources();
         assertTrue("Styled text view: style",
                 mTextView.getTypeface().isItalic() || (mTextView.getPaint().getTextSkewX() < 0));
         assertEquals("Styled text view: color", mTextView.getTextColors().getDefaultColor(),
                 res.getColor(R.color.text_color));
         assertEquals("Styled text view: size", mTextView.getTextSize(),
-                (float) res.getDimensionPixelSize(R.dimen.text_medium_size));
+                (float) res.getDimensionPixelSize(R.dimen.text_medium_size), 1.0f);
     }
 
-    @UiThreadTest
+    @Test
     @SmallTest
     public void testCompoundDrawablesRelative() throws Throwable {
-        createAndAddTextView();
-
         final Drawable drawableStart = new ColorDrawable(0xFFFF0000);
         drawableStart.setBounds(0, 0, 20, 20);
         final Drawable drawableTop = new ColorDrawable(0xFF00FF00);
@@ -200,9 +140,9 @@
         final Drawable drawableEnd = new ColorDrawable(0xFF0000FF);
         drawableEnd.setBounds(0, 0, 25, 20);
 
-        mTextView.setText(R.string.test_text_medium);
-        TextViewCompat.setCompoundDrawablesRelative(mTextView, drawableStart, drawableTop,
-                drawableEnd, null);
+        onView(withId(R.id.text_view)).perform(setText(R.string.test_text_medium));
+        onView(withId(R.id.text_view)).perform(setCompoundDrawablesRelative(drawableStart,
+                drawableTop, drawableEnd, null));
 
         final Drawable[] drawablesAbsolute = mTextView.getCompoundDrawables();
 
@@ -227,12 +167,10 @@
         assertNull("Compound drawable: bottom", drawablesAbsolute[3]);
     }
 
-    @UiThreadTest
+    @Test
     @SmallTest
     public void testCompoundDrawablesRelativeRtl() throws Throwable {
-        createAndAddTextView();
-
-        ViewCompat.setLayoutDirection(mTextView, ViewCompat.LAYOUT_DIRECTION_RTL);
+        onView(withId(R.id.text_view)).perform(setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_RTL));
 
         final Drawable drawableStart = new ColorDrawable(0xFFFF0000);
         drawableStart.setBounds(0, 0, 20, 20);
@@ -241,9 +179,9 @@
         final Drawable drawableEnd = new ColorDrawable(0xFF0000FF);
         drawableEnd.setBounds(0, 0, 25, 20);
 
-        mTextView.setText(R.string.test_text_medium);
-        TextViewCompat.setCompoundDrawablesRelative(mTextView, drawableStart, drawableTop,
-                drawableEnd, null);
+        onView(withId(R.id.text_view)).perform(setText(R.string.test_text_medium));
+        onView(withId(R.id.text_view)).perform(setCompoundDrawablesRelative(drawableStart,
+                drawableTop, drawableEnd, null));
 
         // Check to see whether our text view is under RTL mode
         if (ViewCompat.getLayoutDirection(mTextView) != ViewCompat.LAYOUT_DIRECTION_RTL) {
@@ -276,18 +214,16 @@
         assertNull("Compound drawable: bottom", drawablesAbsolute[3]);
     }
 
-    @UiThreadTest
+    @Test
     @SmallTest
     public void testCompoundDrawablesRelativeWithIntrinsicBounds() throws Throwable {
-        createAndAddTextView();
-
         final Drawable drawableStart = new TestDrawable(0xFFFF0000, 30, 20);
         final Drawable drawableEnd = new TestDrawable(0xFF0000FF, 25, 45);
         final Drawable drawableBottom = new TestDrawable(0xFF00FF00, 15, 35);
 
-        mTextView.setText(R.string.test_text_long);
-        TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(mTextView, drawableStart,
-                null, drawableEnd, drawableBottom);
+        onView(withId(R.id.text_view)).perform(setText(R.string.test_text_long));
+        onView(withId(R.id.text_view)).perform(setCompoundDrawablesRelativeWithIntrinsicBounds(
+                drawableStart, null, drawableEnd, drawableBottom));
 
         final Drawable[] drawablesAbsolute = mTextView.getCompoundDrawables();
 
@@ -312,20 +248,18 @@
                 drawablesAbsolute[3].getBounds().height(), 35);
     }
 
-    @UiThreadTest
+    @Test
     @SmallTest
     public void testCompoundDrawablesRelativeWithIntrinsicBoundsRtl() throws Throwable {
-        createAndAddTextView();
-
-        ViewCompat.setLayoutDirection(mTextView, ViewCompat.LAYOUT_DIRECTION_RTL);
+        onView(withId(R.id.text_view)).perform(setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_RTL));
 
         final Drawable drawableStart = new TestDrawable(0xFFFF0000, 30, 20);
         final Drawable drawableEnd = new TestDrawable(0xFF0000FF, 25, 45);
         final Drawable drawableBottom = new TestDrawable(0xFF00FF00, 15, 35);
 
-        mTextView.setText(R.string.test_text_long);
-        TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(mTextView, drawableStart,
-                null, drawableEnd, drawableBottom);
+        onView(withId(R.id.text_view)).perform(setText(R.string.test_text_long));
+        onView(withId(R.id.text_view)).perform(setCompoundDrawablesRelativeWithIntrinsicBounds(
+                drawableStart, null, drawableEnd, drawableBottom));
 
         // Check to see whether our text view is under RTL mode
         if (ViewCompat.getLayoutDirection(mTextView) != ViewCompat.LAYOUT_DIRECTION_RTL) {
@@ -358,18 +292,16 @@
                 drawablesAbsolute[3].getBounds().height(), 35);
     }
 
-    @UiThreadTest
+    @Test
     @MediumTest
     public void testCompoundDrawablesRelativeWithIntrinsicBoundsById() throws Throwable {
-        createAndAddTextView();
-
-        mTextView.setText(R.string.test_text_long);
-        TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(mTextView,
+        onView(withId(R.id.text_view)).perform(setText(R.string.test_text_long));
+        onView(withId(R.id.text_view)).perform(setCompoundDrawablesRelativeWithIntrinsicBounds(
                 R.drawable.test_drawable_red, 0,
-                R.drawable.test_drawable_green, R.drawable.test_drawable_blue);
+                R.drawable.test_drawable_green, R.drawable.test_drawable_blue));
 
         final Drawable[] drawablesAbsolute = mTextView.getCompoundDrawables();
-        final Resources res = getActivity().getResources();
+        final Resources res = mActivityTestRule.getActivity().getResources();
 
         // The entire left drawable should be the specific red color
         TestUtils.assertAllPixelsOfColor("Compound drawable: left color",
@@ -404,17 +336,15 @@
                 res.getDimensionPixelSize(R.dimen.drawable_small_size));
     }
 
-    @UiThreadTest
+    @Test
     @MediumTest
     public void testCompoundDrawablesRelativeWithIntrinsicBoundsByIdRtl() throws Throwable {
-        createAndAddTextView();
+        onView(withId(R.id.text_view)).perform(setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_RTL));
 
-        ViewCompat.setLayoutDirection(mTextView, ViewCompat.LAYOUT_DIRECTION_RTL);
-
-        mTextView.setText(R.string.test_text_long);
-        TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(mTextView,
+        onView(withId(R.id.text_view)).perform(setText(R.string.test_text_long));
+        onView(withId(R.id.text_view)).perform(setCompoundDrawablesRelativeWithIntrinsicBounds(
                 R.drawable.test_drawable_red, 0,
-                R.drawable.test_drawable_green, R.drawable.test_drawable_blue);
+                R.drawable.test_drawable_green, R.drawable.test_drawable_blue));
 
         // Check to see whether our text view is under RTL mode
         if (ViewCompat.getLayoutDirection(mTextView) != ViewCompat.LAYOUT_DIRECTION_RTL) {
@@ -423,7 +353,7 @@
         }
 
         final Drawable[] drawablesAbsolute = mTextView.getCompoundDrawables();
-        final Resources res = getActivity().getResources();
+        final Resources res = mActivityTestRule.getActivity().getResources();
 
         // The entire left / end drawable should be the specific green color
         TestUtils.assertAllPixelsOfColor("Compound drawable: left color",
diff --git a/v4/tests/java/android/support/v4/widget/test/TextViewTestActivity.java b/v4/tests/java/android/support/v4/widget/TextViewTestActivity.java
similarity index 68%
rename from v4/tests/java/android/support/v4/widget/test/TextViewTestActivity.java
rename to v4/tests/java/android/support/v4/widget/TextViewTestActivity.java
index 7366127..dcaab70 100644
--- a/v4/tests/java/android/support/v4/widget/test/TextViewTestActivity.java
+++ b/v4/tests/java/android/support/v4/widget/TextViewTestActivity.java
@@ -14,11 +14,14 @@
  * limitations under the License.
  */
 
-package android.support.v4.widget.test;
+package android.support.v4.widget;
 
+import android.support.v4.BaseTestActivity;
+import android.support.v4.test.R;
 
-import android.app.Activity;
-
-public class TextViewTestActivity extends Activity {
-
+public class TextViewTestActivity extends BaseTestActivity {
+    @Override
+    protected int getContentViewLayoutResId() {
+        return R.layout.text_view_activity;
+    }
 }
diff --git a/v4/tests/res/layout/text_view_activity.xml b/v4/tests/res/layout/text_view_activity.xml
new file mode 100644
index 0000000..ba5d688
--- /dev/null
+++ b/v4/tests/res/layout/text_view_activity.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.
+-->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/content"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <TextView
+        android:id="@+id/text_view"
+        android:layout_width="200dip"
+        android:layout_height="60dip" />
+</FrameLayout>
diff --git a/v7/appcompat/build.gradle b/v7/appcompat/build.gradle
index 060f2ae..7687733 100644
--- a/v7/appcompat/build.gradle
+++ b/v7/appcompat/build.gradle
@@ -1,4 +1,4 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
 
 archivesBaseName = 'appcompat-v7'
 
@@ -15,7 +15,7 @@
 }
 
 android {
-    compileSdkVersion 'current'
+    compileSdkVersion project.ext.currentSdk
 
     defaultConfig {
         minSdkVersion 7
diff --git a/v7/appcompat/res/values-v23/styles_base.xml b/v7/appcompat/res/values-v23/styles_base.xml
index 3b0e55c..56563c7 100644
--- a/v7/appcompat/res/values-v23/styles_base.xml
+++ b/v7/appcompat/res/values-v23/styles_base.xml
@@ -23,4 +23,6 @@
 
     <style name="Base.Widget.AppCompat.RatingBar.Small" parent="android:Widget.Material.RatingBar.Small" />
 
+    <style name="Base.Widget.AppCompat.Spinner.Underlined" parent="android:Widget.Material.Spinner.Underlined" />
+
 </resources>
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java b/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java
index 8515409..734f46f 100644
--- a/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java
@@ -21,6 +21,7 @@
 import android.os.Bundle;
 import android.support.annotation.CallSuper;
 import android.support.annotation.LayoutRes;
+import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.StyleRes;
 import android.support.v4.app.ActivityCompat;
@@ -96,20 +97,20 @@
     }
 
     /**
-     * Set a {@link android.widget.Toolbar Toolbar} to act as the {@link android.support.v7.app.ActionBar} for this
-     * Activity window.
+     * Set a {@link android.widget.Toolbar Toolbar} to act as the
+     * {@link android.support.v7.app.ActionBar} for this Activity window.
      *
      * <p>When set to a non-null value the {@link #getActionBar()} method will return
-     * an {@link android.support.v7.app.ActionBar} object that can be used to control the given toolbar as if it were
-     * a traditional window decor action bar. The toolbar's menu will be populated with the
-     * Activity's options menu and the navigation button will be wired through the standard
-     * {@link android.R.id#home home} menu select action.</p>
+     * an {@link android.support.v7.app.ActionBar} object that can be used to control the given
+     * toolbar as if it were a traditional window decor action bar. The toolbar's menu will be
+     * populated with the Activity's options menu and the navigation button will be wired through
+     * the standard {@link android.R.id#home home} menu select action.</p>
      *
      * <p>In order to use a Toolbar within the Activity's window content the application
      * must not request the window feature
      * {@link android.view.Window#FEATURE_ACTION_BAR FEATURE_SUPPORT_ACTION_BAR}.</p>
      *
-     * @param toolbar Toolbar to set as the Activity's action bar
+     * @param toolbar Toolbar to set as the Activity's action bar, or {@code null} to clear it
      */
     public void setSupportActionBar(@Nullable Toolbar toolbar) {
         getDelegate().setSupportActionBar(toolbar);
@@ -221,7 +222,7 @@
      * @param mode The new action mode.
      */
     @CallSuper
-    public void onSupportActionModeStarted(ActionMode mode) {
+    public void onSupportActionModeStarted(@NonNull ActionMode mode) {
     }
 
     /**
@@ -231,7 +232,7 @@
      * @param mode The action mode that just finished.
      */
     @CallSuper
-    public void onSupportActionModeFinished(ActionMode mode) {
+    public void onSupportActionModeFinished(@NonNull ActionMode mode) {
     }
 
     /**
@@ -245,11 +246,18 @@
      */
     @Nullable
     @Override
-    public ActionMode onWindowStartingSupportActionMode(ActionMode.Callback callback) {
+    public ActionMode onWindowStartingSupportActionMode(@NonNull ActionMode.Callback callback) {
         return null;
     }
 
-    public ActionMode startSupportActionMode(ActionMode.Callback callback) {
+    /**
+     * Start an action mode.
+     *
+     * @param callback Callback that will manage lifecycle events for this context mode
+     * @return The ContextMode that was started, or null if it was canceled
+     */
+    @Nullable
+    public ActionMode startSupportActionMode(@NonNull ActionMode.Callback callback) {
         return getDelegate().startSupportActionMode(callback);
     }
 
@@ -304,7 +312,7 @@
      * @param builder An empty TaskStackBuilder - the application should add intents representing
      *                the desired task stack
      */
-    public void onCreateSupportNavigateUpTaskStack(TaskStackBuilder builder) {
+    public void onCreateSupportNavigateUpTaskStack(@NonNull TaskStackBuilder builder) {
         builder.addParentStack(this);
     }
 
@@ -323,7 +331,7 @@
      * @param builder A TaskStackBuilder that has been populated with Intents by
      *                onCreateNavigateUpTaskStack.
      */
-    public void onPrepareSupportNavigateUpTaskStack(TaskStackBuilder builder) {
+    public void onPrepareSupportNavigateUpTaskStack(@NonNull TaskStackBuilder builder) {
     }
 
     /**
@@ -403,7 +411,7 @@
      * @return true if navigating up should recreate a new task stack, false if the same task
      *         should be used for the destination
      */
-    public boolean supportShouldUpRecreateTask(Intent targetIntent) {
+    public boolean supportShouldUpRecreateTask(@NonNull Intent targetIntent) {
         return NavUtils.shouldUpRecreateTask(this, targetIntent);
     }
 
@@ -419,7 +427,7 @@
      *
      * @param upIntent An intent representing the target destination for up navigation
      */
-    public void supportNavigateUpTo(Intent upIntent) {
+    public void supportNavigateUpTo(@NonNull Intent upIntent) {
         NavUtils.navigateUpTo(this, upIntent);
     }
 
@@ -473,6 +481,7 @@
     /**
      * @return The {@link AppCompatDelegate} being used by this Activity.
      */
+    @NonNull
     public AppCompatDelegate getDelegate() {
         if (mDelegate == null) {
             mDelegate = AppCompatDelegate.create(this, this);
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatDelegate.java b/v7/appcompat/src/android/support/v7/app/AppCompatDelegate.java
index becfe6c..2f887cc 100644
--- a/v7/appcompat/src/android/support/v7/app/AppCompatDelegate.java
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatDelegate.java
@@ -26,6 +26,7 @@
 import android.support.annotation.IntDef;
 import android.support.annotation.LayoutRes;
 import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.v4.app.FragmentActivity;
 import android.support.v4.view.WindowCompat;
 import android.support.v7.appcompat.R;
@@ -187,6 +188,7 @@
      *
      * @return AppCompat's action bar, or null if it does not have one.
      */
+    @Nullable
     public abstract ActionBar getSupportActionBar();
 
     /**
@@ -202,9 +204,9 @@
      * must not request the window feature
      * {@link AppCompatDelegate#FEATURE_SUPPORT_ACTION_BAR FEATURE_SUPPORT_ACTION_BAR}.</p>
      *
-     * @param toolbar Toolbar to set as the Activity's action bar
+     * @param toolbar Toolbar to set as the Activity's action bar, or {@code null} to clear it
      */
-    public abstract void setSupportActionBar(Toolbar toolbar);
+    public abstract void setSupportActionBar(@Nullable Toolbar toolbar);
 
     /**
      * Return the value of this call from your {@link Activity#getMenuInflater()}
@@ -271,7 +273,7 @@
     /**
      * Should be called from {@link Activity#onTitleChanged(CharSequence, int)}}
      */
-    public abstract void setTitle(CharSequence title);
+    public abstract void setTitle(@Nullable CharSequence title);
 
     /**
      * Should be called from {@link Activity#invalidateOptionsMenu()}} or
@@ -288,6 +290,7 @@
      * Returns an {@link ActionBarDrawerToggle.Delegate} which can be returned from your Activity
      * if it implements {@link ActionBarDrawerToggle.DelegateProvider}.
      */
+    @Nullable
     public abstract ActionBarDrawerToggle.Delegate getDrawerToggleDelegate();
 
     /**
@@ -317,7 +320,8 @@
      * @param callback Callback that will manage lifecycle events for this context mode
      * @return The ContextMode that was started, or null if it was canceled
      */
-    public abstract ActionMode startSupportActionMode(ActionMode.Callback callback);
+    @Nullable
+    public abstract ActionMode startSupportActionMode(@NonNull ActionMode.Callback callback);
 
     /**
      * Installs AppCompat's {@link android.view.LayoutInflater} Factory so that it can replace
@@ -348,7 +352,7 @@
      * {@link android.view.LayoutInflater LayoutInflater} factory, and have therefore not
      * installed the default factory via {@link #installViewFactory()}.
      */
-    public abstract View createView(View parent, String name, @NonNull Context context,
+    public abstract View createView(@Nullable View parent, String name, @NonNull Context context,
             @NonNull AttributeSet attrs);
 
     /**
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java
index 05c8ec3..cc13909 100644
--- a/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java
@@ -206,11 +206,18 @@
             ab.onDestroy();
         }
 
-        ToolbarActionBar tbab = new ToolbarActionBar(toolbar, ((Activity) mContext).getTitle(),
-                mAppCompatWindowCallback);
-        mActionBar = tbab;
-        mWindow.setCallback(tbab.getWrappedWindowCallback());
-        tbab.invalidateOptionsMenu();
+        if (toolbar != null) {
+            final ToolbarActionBar tbab = new ToolbarActionBar(toolbar,
+                    ((Activity) mContext).getTitle(), mAppCompatWindowCallback);
+            mActionBar = tbab;
+            mWindow.setCallback(tbab.getWrappedWindowCallback());
+        } else {
+            mActionBar = null;
+            // Re-set the original window callback since we may have already set a Toolbar wrapper
+            mWindow.setCallback(mAppCompatWindowCallback);
+        }
+
+        invalidateOptionsMenu();
     }
 
     @Override
diff --git a/v7/appcompat/tests/AndroidManifest.xml b/v7/appcompat/tests/AndroidManifest.xml
index 6f6fefb..3116eaf 100644
--- a/v7/appcompat/tests/AndroidManifest.xml
+++ b/v7/appcompat/tests/AndroidManifest.xml
@@ -24,7 +24,10 @@
             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:theme="@style/Theme.AppCompat"
+        android:supportsRtl="true">
+
         <uses-library android:name="android.test.runner"/>
 
         <activity
@@ -42,6 +45,11 @@
                 android:theme="@style/Theme.SampleDrawerLayout" />
 
         <activity
+                android:name="android.support.v7.app.DrawerLayoutDoubleActivity"
+                android:label="@string/drawer_layout_activity"
+                android:theme="@style/Theme.SampleDrawerLayout" />
+
+        <activity
                 android:name="android.support.v7.app.DrawerDynamicLayoutActivity"
                 android:label="@string/drawer_layout_activity"
                 android:theme="@style/Theme.SampleDrawerLayout" />
@@ -53,7 +61,7 @@
 
         <activity
                 android:name="android.support.v7.widget.PopupTestActivity"
-                android:label="@string/list_popup_window_activity"
+                android:label="@string/popup_activity"
                 android:theme="@style/Theme.AppCompat.Light" />
 
         <activity
diff --git a/v7/appcompat/tests/res/layout/drawer_double_layout.xml b/v7/appcompat/tests/res/layout/drawer_double_layout.xml
new file mode 100644
index 0000000..1af5be3
--- /dev/null
+++ b/v7/appcompat/tests/res/layout/drawer_double_layout.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.
+-->
+
+<!--
+    A DrawerLayout is indended to be used as the top-level content view
+    using match_parent for both width and height to consume the full space available.
+-->
+<android.support.v7.custom.CustomDrawerLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/drawer_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true">
+    <!-- As the main content view, the view below consumes the entire
+         space available using match_parent in both dimensions. Note that
+         this child does not specify android:layout_gravity attribute. -->
+    <LinearLayout
+        android:id="@+id/content"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+        <!-- This will be set as the support action bar of the activity at runtime.
+             It needs to be a dynamic runtime call for correct vertical layering of
+             the drawer and the toolbar. -->
+        <android.support.v7.widget.Toolbar
+            android:id="@+id/toolbar"
+            android:layout_width="match_parent"
+            android:layout_height="?attr/actionBarSize" />
+
+        <ScrollView
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:scrollbarStyle="outsideOverlay">
+            <TextView
+                android:id="@+id/content_text"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:text="@string/drawer_layout_summary"
+                android:textAppearance="?android:attr/textAppearanceMedium"
+                android:padding="16dp"/>
+        </ScrollView>
+    </LinearLayout>
+
+    <!-- android:layout_gravity="start" tells DrawerLayout to treat
+         this as a sliding drawer on the starting side, which is
+         left for left-to-right locales. The drawer is given a fixed
+         width in dp and extends the full height of the container. A
+         solid background is used for contrast with the content view.
+         android:fitsSystemWindows="true" tells the system to have
+         DrawerLayout span the full height of the screen, including the
+         system status bar on Lollipop+ versions of the plaform. -->
+    <ListView
+            android:id="@+id/start_drawer"
+            android:layout_width="300dp"
+            android:layout_height="match_parent"
+            android:layout_gravity="start"
+            android:background="#ff333333"
+            android:fitsSystemWindows="true"/>
+
+    <!-- End drawer with fixed width. -->
+    <FrameLayout
+        android:id="@+id/end_drawer"
+        android:layout_width="250dp"
+        android:layout_height="match_parent"
+        android:layout_gravity="end"
+        android:background="#444" />
+</android.support.v7.custom.CustomDrawerLayout>
+
diff --git a/v7/appcompat/tests/res/layout/drawer_layout.xml b/v7/appcompat/tests/res/layout/drawer_layout.xml
index 7bed9df..c65eb72 100644
--- a/v7/appcompat/tests/res/layout/drawer_layout.xml
+++ b/v7/appcompat/tests/res/layout/drawer_layout.xml
@@ -27,34 +27,32 @@
     <!-- As the main content view, the view below consumes the entire
          space available using match_parent in both dimensions. Note that
          this child does not specify android:layout_gravity attribute. -->
-    <FrameLayout
-            android:id="@+id/content"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent">
+    <LinearLayout
+        android:id="@+id/content"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
         <!-- This will be set as the support action bar of the activity at runtime.
              It needs to be a dynamic runtime call for correct vertical layering of
              the drawer and the toolbar. -->
         <android.support.v7.widget.Toolbar
-                android:id="@+id/toolbar"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content" />
-        <!-- Note layout_marginTop attribute with action bar height as the value.
-             This "pushes" down the main content so that it doesn't overlap with
-             the toolbar. -->
+            android:id="@+id/toolbar"
+            android:layout_width="match_parent"
+            android:layout_height="?attr/actionBarSize" />
+
         <ScrollView
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:scrollbarStyle="outsideOverlay">
+            <TextView
+                android:id="@+id/content_text"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
-                android:layout_marginTop="?attr/actionBarSize"
-                android:scrollbarStyle="outsideOverlay">
-            <TextView
-                    android:id="@+id/content_text"
-                    android:layout_width="match_parent"
-                    android:layout_height="match_parent"
-                    android:text="@string/drawer_layout_summary"
-                    android:textAppearance="?android:attr/textAppearanceMedium"
-                    android:padding="16dp"/>
+                android:text="@string/drawer_layout_summary"
+                android:textAppearance="?android:attr/textAppearanceMedium"
+                android:padding="16dp"/>
         </ScrollView>
-    </FrameLayout>
+    </LinearLayout>
 
     <!-- android:layout_gravity="start" tells DrawerLayout to treat
          this as a sliding drawer on the starting side, which is
diff --git a/v7/appcompat/tests/res/layout/popup_test_activity.xml b/v7/appcompat/tests/res/layout/popup_test_activity.xml
index 152ddaf..9ea488f 100644
--- a/v7/appcompat/tests/res/layout/popup_test_activity.xml
+++ b/v7/appcompat/tests/res/layout/popup_test_activity.xml
@@ -24,6 +24,6 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center_horizontal"
-        android:text="@string/list_popup_window_show" />
+        android:text="@string/popup_show" />
 </FrameLayout>
 
diff --git a/v7/appcompat/tests/res/layout/toolbar_decor_content.xml b/v7/appcompat/tests/res/layout/toolbar_decor_content.xml
index e2f6b3f..e167c9b 100644
--- a/v7/appcompat/tests/res/layout/toolbar_decor_content.xml
+++ b/v7/appcompat/tests/res/layout/toolbar_decor_content.xml
@@ -24,4 +24,9 @@
             android:layout_width="match_parent"
             android:layout_height="?attr/actionBarSize"/>
 
+    <android.support.v7.custom.FitWindowsContentLayout
+            android:id="@+id/test_content"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"/>
+
 </LinearLayout>
\ No newline at end of file
diff --git a/v7/appcompat/tests/res/layout/window_decor_content.xml b/v7/appcompat/tests/res/layout/window_decor_content.xml
index 657f51c..2d79660 100644
--- a/v7/appcompat/tests/res/layout/window_decor_content.xml
+++ b/v7/appcompat/tests/res/layout/window_decor_content.xml
@@ -19,4 +19,9 @@
               android:layout_width="match_parent"
               android:layout_height="match_parent">
 
+    <android.support.v7.custom.FitWindowsContentLayout
+            android:id="@+id/test_content"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"/>
+
 </LinearLayout>
\ No newline at end of file
diff --git a/v7/appcompat/tests/res/menu/popup_menu.xml b/v7/appcompat/tests/res/menu/popup_menu.xml
new file mode 100644
index 0000000..f50efc5
--- /dev/null
+++ b/v7/appcompat/tests/res/menu/popup_menu.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 Google Inc.
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/action_highlight"
+          android:title="@string/popup_menu_highlight" />
+    <item android:id="@+id/action_edit"
+          android:title="@string/popup_menu_edit" />
+    <item android:id="@+id/action_delete"
+          android:title="@string/popup_menu_delete" />
+    <item android:id="@+id/action_ignore"
+          android:title="@string/popup_menu_ignore" />
+    <item android:id="@+id/action_share"
+          android:title="@string/popup_menu_share">
+        <menu>
+            <item android:id="@+id/action_share_email"
+                  android:title="@string/popup_menu_share_email" />
+            <item android:id="@+id/action_share_circles"
+                  android:title="@string/popup_menu_share_circles" />
+        </menu>
+    </item>
+    <item android:id="@+id/action_print"
+          android:title="@string/popup_menu_print" />
+</menu>
diff --git a/v7/appcompat/tests/res/values/strings.xml b/v7/appcompat/tests/res/values/strings.xml
index b7347a2..e094078 100644
--- a/v7/appcompat/tests/res/values/strings.xml
+++ b/v7/appcompat/tests/res/values/strings.xml
@@ -21,8 +21,16 @@
     <string name="drawer_open">Open navigation drawer</string>
     <string name="drawer_close">Close navigation drawer</string>
 
-    <string name="list_popup_window_activity">List popup window</string>
-    <string name="list_popup_window_show">Show popup</string>
+    <string name="popup_activity">Popup activity</string>
+    <string name="popup_show">Show popup</string>
+    <string name="popup_menu_highlight">Highlight</string>
+    <string name="popup_menu_edit">Edit</string>
+    <string name="popup_menu_delete">Delete</string>
+    <string name="popup_menu_ignore">Ignore</string>
+    <string name="popup_menu_share">Share</string>
+    <string name="popup_menu_share_email">Via email</string>
+    <string name="popup_menu_share_circles">To my circles</string>
+    <string name="popup_menu_print">Print</string>
 
     <string name="alert_dialog_activity">Alert dialog</string>
     <string name="alert_dialog_show">Show alert dialog</string>
diff --git a/v7/appcompat/tests/src/android/support/v7/app/BaseBasicsTestCase.java b/v7/appcompat/tests/src/android/support/v7/app/BaseBasicsTestCase.java
index ed421a9..d017d52 100644
--- a/v7/appcompat/tests/src/android/support/v7/app/BaseBasicsTestCase.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/BaseBasicsTestCase.java
@@ -16,12 +16,24 @@
 
 package android.support.v7.app;
 
-import android.support.v7.testutils.BaseTestActivity;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
-
 import org.junit.Test;
 
+import android.os.Build;
+import android.support.v7.appcompat.test.R;
+import android.support.v7.custom.FitWindowsContentLayout;
+import android.support.v7.testutils.BaseTestActivity;
+import android.support.v7.testutils.TestUtils;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.View;
+import android.view.WindowInsets;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
 public abstract class BaseBasicsTestCase<A extends BaseTestActivity>
         extends BaseInstrumentationTestCase<A> {
 
@@ -56,28 +68,53 @@
     }
 
     @Test
-    @MediumTest
+    @SmallTest
     public void testMenuInvalidationAfterDestroy() throws Throwable {
         final A activity = getActivity();
+        // Reset to make sure that we don't have a menu currently
+        activity.reset();
+        assertNull(activity.getMenu());
 
-        getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                // Reset to make sure that we don't have a menu currently
-                activity.reset();
-                assertNull(activity.getMenu());
+        // Now destroy the Activity
+        activity.finish();
+        TestUtils.waitForActivityDestroyed(activity);
 
-                // Now dispatch a menu invalidation
-                activity.supportInvalidateOptionsMenu();
-                // Now destroy the Activity
-                getInstrumentation().callActivityOnDestroy(activity);
-            }
-        });
-
-        // Now wait for any delayed menu population
-        Thread.sleep(100);
+        // Now dispatch a menu invalidation and wait for an idle sync
+        activity.supportInvalidateOptionsMenu();
+        getInstrumentation().waitForIdleSync();
 
         // Make sure that we don't have a menu given to use after being destroyed
         assertNull(activity.getMenu());
     }
+
+    @Test
+    @SmallTest
+    public void testFitSystemWindowsReachesContent() {
+        final FitWindowsContentLayout content =
+                (FitWindowsContentLayout) getActivity().findViewById(R.id.test_content);
+        assertNotNull(content);
+        assertTrue(content.getFitsSystemWindowsCalled());
+    }
+
+    @Test
+    @SmallTest
+    public void testOnApplyWindowInsetsReachesContent() {
+        if (Build.VERSION.SDK_INT < 21) {
+            // OnApplyWindowInsetsListener is only available on API 21+
+            return;
+        }
+
+        final View content = getActivity().findViewById(R.id.test_content);
+        assertNotNull(content);
+
+        final AtomicBoolean applyWindowInsetsCalled = new AtomicBoolean();
+        content.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
+            @Override
+            public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) {
+                applyWindowInsetsCalled.set(true);
+                return windowInsets;
+            }
+        });
+        assertTrue(applyWindowInsetsCalled.get());
+    }
 }
diff --git a/v7/appcompat/tests/src/android/support/v7/app/BaseInstrumentationTestCase.java b/v7/appcompat/tests/src/android/support/v7/app/BaseInstrumentationTestCase.java
index 6591ff7..76ca5dc 100644
--- a/v7/appcompat/tests/src/android/support/v7/app/BaseInstrumentationTestCase.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/BaseInstrumentationTestCase.java
@@ -16,28 +16,50 @@
 
 package android.support.v7.app;
 
-import org.junit.Before;
+import org.junit.Rule;
 import org.junit.runner.RunWith;
 
 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.test.ActivityInstrumentationTestCase2;
 
 @RunWith(AndroidJUnit4.class)
-public abstract class BaseInstrumentationTestCase<A extends Activity>
-        extends ActivityInstrumentationTestCase2<A> {
+public abstract class BaseInstrumentationTestCase<A extends Activity> {
+
+    @Rule
+    public final ActivityTestRule<A> mActivityTestRule;
 
     protected BaseInstrumentationTestCase(Class<A> activityClass) {
-        super(activityClass);
+        mActivityTestRule = new ActivityTestRule<A>(activityClass);
     }
 
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        injectInstrumentation(InstrumentationRegistry.getInstrumentation());
-        getActivity();
+    @Deprecated
+    public A getActivity() {
+        return mActivityTestRule.getActivity();
+    }
+
+    @Deprecated
+    public Instrumentation getInstrumentation() {
+        return InstrumentationRegistry.getInstrumentation();
+    }
+
+    @Deprecated
+    public void runTestOnUiThread(final Runnable r) throws Throwable {
+        final Throwable[] exceptions = new Throwable[1];
+        getInstrumentation().runOnMainSync(new Runnable() {
+            public void run() {
+                try {
+                    r.run();
+                } catch (Throwable throwable) {
+                    exceptions[0] = throwable;
+                }
+            }
+        });
+        if (exceptions[0] != null) {
+            throw exceptions[0];
+        }
     }
 
 }
diff --git a/v7/appcompat/tests/src/android/support/v7/app/BaseKeyEventsTestCase.java b/v7/appcompat/tests/src/android/support/v7/app/BaseKeyEventsTestCase.java
index d0beb59..debf278 100644
--- a/v7/appcompat/tests/src/android/support/v7/app/BaseKeyEventsTestCase.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/BaseKeyEventsTestCase.java
@@ -16,10 +16,10 @@
 
 package android.support.v7.app;
 
-import android.support.v7.testutils.BaseTestActivity;
 import org.junit.Test;
 
 import android.support.v7.appcompat.test.R;
+import android.support.v7.testutils.BaseTestActivity;
 import android.support.v7.view.ActionMode;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -29,6 +29,11 @@
 
 import java.util.concurrent.atomic.AtomicBoolean;
 
+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 BaseKeyEventsTestCase<A extends BaseTestActivity>
         extends BaseInstrumentationTestCase<A> {
 
@@ -73,7 +78,7 @@
         getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
         getInstrumentation().waitForIdleSync();
 
-        assertFalse("Activity was not destroyed", getActivity().isDestroyed());
+        assertFalse("Activity was not finished", getActivity().isFinishing());
         assertTrue("ActionMode was destroyed", destroyed.get());
     }
 
@@ -101,7 +106,7 @@
         }
 
         // Check that the Activity is still running and the SearchView is not expanded
-        assertFalse("Activity was not destroyed", getActivity().isDestroyed());
+        assertFalse("Activity was not finished", getActivity().isFinishing());
         assertFalse("SearchView was collapsed", getActivity().isSearchViewExpanded());
     }
 
@@ -129,15 +134,15 @@
     }
 
     @Test
-    @MediumTest
-    public void testBackPressWithEmptyMenuDestroysActivity() throws InterruptedException {
+    @SmallTest
+    public void testBackPressWithEmptyMenuFinishesActivity() throws InterruptedException {
         repopulateWithEmptyMenu();
 
         getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
         getInstrumentation().waitForIdleSync();
 
         getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
-        waitAssertDestroyed();
+        assertTrue(getActivity().isFinishing());
     }
 
     @Test
@@ -171,25 +176,13 @@
         assertEquals("onKeyDown event matches", KeyEvent.KEYCODE_MENU, upEvent.getKeyCode());
     }
 
-    private void waitAssertDestroyed() throws InterruptedException {
-        int count = 0;
-        while (count++ < 10) {
-            if (!getActivity().isDestroyed()) {
-                Thread.sleep(50);
-            } else {
-                break;
-            }
-        }
-        assertTrue("Activity destroyed", getActivity().isDestroyed());
-    }
-
     private void repopulateWithEmptyMenu() throws InterruptedException {
         int count = 0;
         getActivity().setShouldPopulateOptionsMenu(false);
         while (count++ < 10) {
             Menu menu = getActivity().getMenu();
             if (menu == null || menu.size() != 0) {
-                Thread.sleep(50);
+                Thread.sleep(100);
             } else {
                 return;
             }
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 b118eec..f53b8e1 100644
--- a/v7/appcompat/tests/src/android/support/v7/app/BaseKeyboardShortcutsTestCase.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/BaseKeyboardShortcutsTestCase.java
@@ -16,16 +16,19 @@
 
 package android.support.v7.app;
 
-import android.support.v7.testutils.BaseTestActivity;
 import org.junit.Test;
 
 import android.os.SystemClock;
 import android.support.v7.appcompat.test.R;
+import android.support.v7.testutils.BaseTestActivity;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 import android.view.MenuItem;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
 public abstract class BaseKeyboardShortcutsTestCase<A extends BaseTestActivity>
         extends BaseInstrumentationTestCase<A> {
 
diff --git a/v7/appcompat/tests/src/android/support/v7/app/DialogTestCase.java b/v7/appcompat/tests/src/android/support/v7/app/DialogTestCase.java
index 053e971..69fdb5b 100644
--- a/v7/appcompat/tests/src/android/support/v7/app/DialogTestCase.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/DialogTestCase.java
@@ -21,6 +21,9 @@
 import android.app.Dialog;
 import android.os.Bundle;
 
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
 public class DialogTestCase extends BaseInstrumentationTestCase<WindowDecorActionBarActivity> {
 
     public DialogTestCase() {
diff --git a/v7/appcompat/tests/src/android/support/v7/app/DrawerDynamicLayoutTest.java b/v7/appcompat/tests/src/android/support/v7/app/DrawerDynamicLayoutTest.java
index 90540d0..de49149 100755
--- a/v7/appcompat/tests/src/android/support/v7/app/DrawerDynamicLayoutTest.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/DrawerDynamicLayoutTest.java
@@ -15,6 +15,13 @@
  */
 package android.support.v7.app;
 
+import android.support.test.InstrumentationRegistry;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.After;
+import org.junit.Test;
+
 import android.support.annotation.LayoutRes;
 import android.support.test.espresso.UiController;
 import android.support.test.espresso.ViewAction;
@@ -22,11 +29,6 @@
 import android.test.suitebuilder.annotation.SmallTest;
 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 org.junit.Test;
 
 import static android.support.test.espresso.Espresso.onView;
 import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
@@ -45,23 +47,16 @@
     }
 
     @After
-    @Override
     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.
-        try {
-            runTestOnUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    final DrawerDynamicLayoutActivity activity = getActivity();
-                    activity.setContentView(R.layout.drawer_dynamic_layout);
-                }
-            });
-        } catch (Throwable t) {
-            throw new Exception(t);
-        }
-
-        super.tearDown();
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                final DrawerDynamicLayoutActivity activity = mActivityTestRule.getActivity();
+                activity.setContentView(R.layout.drawer_dynamic_layout);
+            }
+        });
     }
 
     /**
diff --git a/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutActions.java b/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutActions.java
index 4d2b1be..dabc363 100755
--- a/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutActions.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutActions.java
@@ -27,7 +27,7 @@
 
 public class DrawerLayoutActions {
     /**
-     * Opens the drawer.
+     * Opens the drawer at the specified edge gravity.
      */
     public static ViewAction openDrawer(final int drawerEdgeGravity) {
         return new ViewAction() {
@@ -55,7 +55,35 @@
     }
 
     /**
-     * Closes the drawer.
+     * Opens the drawer.
+     */
+    public static ViewAction openDrawer(final View drawerView) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isAssignableFrom(DrawerLayout.class);
+            }
+
+            @Override
+            public String getDescription() {
+                return "Opens the drawer";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                DrawerLayout drawerLayout = (DrawerLayout) view;
+                drawerLayout.openDrawer(drawerView);
+
+                // Wait for a full second to let the inner ViewDragHelper complete the operation
+                uiController.loopMainThreadForAtLeast(1000);
+            }
+        };
+    }
+
+    /**
+     * Closes the drawer at the specified edge gravity.
      */
     public static ViewAction closeDrawer(final int drawerEdgeGravity) {
         return new ViewAction() {
@@ -81,4 +109,86 @@
             }
         };
     }
+
+    /**
+     * Closes the drawer.
+     */
+    public static ViewAction closeDrawer(final View drawerView) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isAssignableFrom(DrawerLayout.class);
+            }
+
+            @Override
+            public String getDescription() {
+                return "Closes the drawer";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                DrawerLayout drawerLayout = (DrawerLayout) view;
+                drawerLayout.closeDrawer(drawerView);
+
+                // Wait for a full second to let the inner ViewDragHelper complete the operation
+                uiController.loopMainThreadForAtLeast(1000);
+            }
+        };
+    }
+
+    /**
+     * Sets the lock mode for the drawer at the specified edge gravity.
+     */
+    public static ViewAction setDrawerLockMode(final int lockMode, final int drawerEdgeGravity) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isAssignableFrom(DrawerLayout.class);
+            }
+
+            @Override
+            public String getDescription() {
+                return "Sets drawer lock mode";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                DrawerLayout drawerLayout = (DrawerLayout) view;
+                drawerLayout.setDrawerLockMode(lockMode, drawerEdgeGravity);
+
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
+
+    /**
+     * Sets the lock mode for the drawer.
+     */
+    public static ViewAction setDrawerLockMode(final int lockMode, final View drawerView) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isAssignableFrom(DrawerLayout.class);
+            }
+
+            @Override
+            public String getDescription() {
+                return "Sets drawer lock mode";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                DrawerLayout drawerLayout = (DrawerLayout) view;
+                drawerLayout.setDrawerLockMode(lockMode, drawerView);
+
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
 }
diff --git a/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutDoubleActivity.java b/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutDoubleActivity.java
new file mode 100644
index 0000000..b2e0abc
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutDoubleActivity.java
@@ -0,0 +1,119 @@
+/*
+ * 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.res.Configuration;
+import android.os.Bundle;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.appcompat.test.R;
+import android.support.v7.testutils.BaseTestActivity;
+import android.support.v7.testutils.Shakespeare;
+import android.support.v7.widget.Toolbar;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+/**
+ * Test activity for testing various APIs and interactions for DrawerLayout with start and end
+ * drawers.
+ */
+public class DrawerLayoutDoubleActivity extends BaseTestActivity {
+    private DrawerLayout mDrawerLayout;
+    private ListView mStartDrawer;
+    private View mEndDrawer;
+    private TextView mContent;
+
+    private Toolbar mToolbar;
+
+    @Override
+    protected int getContentViewLayoutResId() {
+        return R.layout.drawer_double_layout;
+    }
+
+    @Override
+    protected void onContentViewSet() {
+        super.onContentViewSet();
+
+        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
+        mStartDrawer = (ListView) findViewById(R.id.start_drawer);
+        mEndDrawer = findViewById(R.id.end_drawer);
+        mContent = (TextView) findViewById(R.id.content_text);
+
+        mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
+
+        // The drawer title must be set in order to announce state changes when
+        // accessibility is turned on. This is typically a simple description,
+        // e.g. "Navigation".
+        mDrawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.drawer_title));
+
+        mStartDrawer.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,
+                Shakespeare.TITLES));
+        mStartDrawer.setOnItemClickListener(new DrawerItemClickListener());
+
+        // Find the toolbar in our layout and set it as the support action bar on the activity.
+        // This is required to have the drawer slide "over" the toolbar.
+        mToolbar = (Toolbar) findViewById(R.id.toolbar);
+        mToolbar.setTitle(R.string.drawer_title);
+        setSupportActionBar(mToolbar);
+
+        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+        getSupportActionBar().setDisplayShowHomeEnabled(false);
+
+        // Configure the background color fill of the system status bar (on supported platform
+        // versions) and the toolbar itself. We're using the same color, and android:statusBar
+        // from the theme makes the status bar slightly darker.
+        final int metalBlueColor = getResources().getColor(R.color.drawer_sample_metal_blue);
+        mDrawerLayout.setStatusBarBackgroundColor(metalBlueColor);
+        mToolbar.setBackgroundColor(metalBlueColor);
+    }
+
+    @Override
+    public void onBackPressed() {
+        // Is the start drawer open?
+        if (mDrawerLayout.isDrawerOpen(mStartDrawer)) {
+            // Close the drawer and return.
+            mDrawerLayout.closeDrawer(mStartDrawer);
+            return;
+        }
+
+        // Is the end drawer open?
+        if (mDrawerLayout.isDrawerOpen(mEndDrawer)) {
+            // Close the drawer and return.
+            mDrawerLayout.closeDrawer(mEndDrawer);
+            return;
+        }
+
+        super.onBackPressed();
+    }
+
+    /**
+     * This list item click listener implements very simple view switching by changing
+     * the primary content text. The drawer is closed when a selection is made.
+     */
+    private class DrawerItemClickListener implements ListView.OnItemClickListener {
+        @Override
+        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+            mContent.setText(Shakespeare.DIALOGUE[position]);
+            mToolbar.setTitle(Shakespeare.TITLES[position]);
+            mDrawerLayout.closeDrawer(mStartDrawer);
+        }
+    }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutDoubleTest.java b/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutDoubleTest.java
new file mode 100755
index 0000000..813d5fb
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutDoubleTest.java
@@ -0,0 +1,232 @@
+/*
+ * 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.support.v4.view.ViewCompat;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.appcompat.test.R;
+import android.support.v7.custom.CustomDrawerLayout;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.View;
+import org.junit.Before;
+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.app.DrawerLayoutActions.closeDrawer;
+import static android.support.v7.app.DrawerLayoutActions.openDrawer;
+import static android.support.v7.app.DrawerLayoutActions.setDrawerLockMode;
+import static android.support.v7.testutils.TestUtilsActions.setLayoutDirection;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class DrawerLayoutDoubleTest
+        extends BaseInstrumentationTestCase<DrawerLayoutDoubleActivity> {
+    private CustomDrawerLayout mDrawerLayout;
+
+    private View mStartDrawer;
+
+    private View mEndDrawer;
+
+    private View mContentView;
+
+    public DrawerLayoutDoubleTest() {
+        super(DrawerLayoutDoubleActivity.class);
+    }
+
+    @Before
+    public void setUp() {
+        final DrawerLayoutDoubleActivity activity = mActivityTestRule.getActivity();
+        mDrawerLayout = (CustomDrawerLayout) activity.findViewById(R.id.drawer_layout);
+        mStartDrawer = mDrawerLayout.findViewById(R.id.start_drawer);
+        mEndDrawer = mDrawerLayout.findViewById(R.id.end_drawer);
+        mContentView = mDrawerLayout.findViewById(R.id.content);
+
+        // Close the drawers to reset the state for the next test
+        onView(withId(R.id.drawer_layout)).perform(closeDrawer(mStartDrawer));
+        onView(withId(R.id.drawer_layout)).perform(closeDrawer(mEndDrawer));
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    @SmallTest
+    public void testQueryOpenStateOfNonExistentDrawer() {
+        // Note that we're expecting the isDrawerOpen API call to result in an exception being
+        // thrown since mContentView is not a drawer.
+        assertFalse("Querying open state of a view that is not a drawer",
+                mDrawerLayout.isDrawerOpen(mContentView));
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    @SmallTest
+    public void testQueryVisibleStateOfNonExistentDrawer() {
+        // Note that we're expecting the isDrawerVisible API call to result in an exception being
+        // thrown since mContentView is not a drawer.
+        assertFalse("Querying visible state of a view that is not a drawer",
+                mDrawerLayout.isDrawerVisible(mContentView));
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    @SmallTest
+    public void testOpenNonExistentDrawer() {
+        // Note that we're expecting the openDrawer action to result in an exception being
+        // thrown since mContentView is not a drawer.
+        onView(withId(R.id.drawer_layout)).perform(openDrawer(mContentView));
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    @SmallTest
+    public void testCloseNonExistentDrawer() {
+        // Note that we're expecting the closeDrawer action to result in an exception being
+        // thrown since mContentView is not a drawer.
+        onView(withId(R.id.drawer_layout)).perform(closeDrawer(mContentView));
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    @SmallTest
+    public void testLockNonExistentDrawer() {
+        // Note that we're expecting the setDrawerLockMode action to result in an exception being
+        // thrown since mContentView is not a drawer.
+        onView(withId(R.id.drawer_layout)).perform(
+                setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN, mContentView));
+    }
+
+    private void verifyDrawerOpenClose() {
+        assertFalse("Start drawer is closed in initial state",
+                mDrawerLayout.isDrawerOpen(mStartDrawer));
+        assertFalse("Start drawer is not visible in initial state",
+                mDrawerLayout.isDrawerVisible(mStartDrawer));
+        assertFalse("End drawer is closed in initial state",
+                mDrawerLayout.isDrawerOpen(mEndDrawer));
+        assertFalse("End drawer is not visible in initial state",
+                mDrawerLayout.isDrawerVisible(mEndDrawer));
+
+        // Open the start drawer
+        onView(withId(R.id.drawer_layout)).perform(openDrawer(mStartDrawer));
+        // And check that it's open (with the end drawer closed)
+        assertTrue("Start drawer is now open", mDrawerLayout.isDrawerOpen(mStartDrawer));
+        assertTrue("Start drawer is now visible", mDrawerLayout.isDrawerVisible(mStartDrawer));
+        assertFalse("End drawer is still closed", mDrawerLayout.isDrawerOpen(mEndDrawer));
+        assertFalse("End drawer is still not visible", mDrawerLayout.isDrawerVisible(mEndDrawer));
+
+        // Close the start drawer
+        onView(withId(R.id.drawer_layout)).perform(closeDrawer(mStartDrawer));
+        // And check that both drawers are closed
+        assertFalse("Start drawer is now closed", mDrawerLayout.isDrawerOpen(mStartDrawer));
+        assertFalse("Start drawer is now not visible", mDrawerLayout.isDrawerVisible(mStartDrawer));
+        assertFalse("End drawer is still closed", mDrawerLayout.isDrawerOpen(mEndDrawer));
+        assertFalse("End drawer is still not visible", mDrawerLayout.isDrawerVisible(mEndDrawer));
+
+        // Open the end drawer
+        onView(withId(R.id.drawer_layout)).perform(openDrawer(mEndDrawer));
+        // And check that it's open (with the start drawer closed)
+        assertFalse("Start drawer is still closed", mDrawerLayout.isDrawerOpen(mStartDrawer));
+        assertFalse("Start drawer is still not visible",
+                mDrawerLayout.isDrawerVisible(mStartDrawer));
+        assertTrue("End drawer is now open", mDrawerLayout.isDrawerOpen(mEndDrawer));
+        assertTrue("End drawer is now visible", mDrawerLayout.isDrawerVisible(mEndDrawer));
+
+        // Close the end drawer
+        onView(withId(R.id.drawer_layout)).perform(closeDrawer(mEndDrawer));
+        // And check that both drawers are closed
+        assertFalse("Start drawer is still closed", mDrawerLayout.isDrawerOpen(mStartDrawer));
+        assertFalse("Start drawer is still not visible",
+                mDrawerLayout.isDrawerVisible(mStartDrawer));
+        assertFalse("End drawer is still closed", mDrawerLayout.isDrawerOpen(mEndDrawer));
+        assertFalse("End drawer is still not visible", mDrawerLayout.isDrawerVisible(mEndDrawer));
+    }
+
+    @Test
+    @SmallTest
+    public void testDrawerOpenCloseLtr() {
+        onView(withId(R.id.drawer_layout)).perform(
+                setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_LTR));
+
+        verifyDrawerOpenClose();
+    }
+
+    @Test
+    @SmallTest
+    public void testDrawerOpenCloseRtl() {
+        onView(withId(R.id.drawer_layout)).perform(
+                setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_RTL));
+
+        verifyDrawerOpenClose();
+    }
+
+    private void verifyDrawerLockUnlock() {
+        assertEquals("Start drawer is unlocked in initial state",
+                DrawerLayout.LOCK_MODE_UNLOCKED, mDrawerLayout.getDrawerLockMode(mStartDrawer));
+        assertEquals("End drawer is unlocked in initial state",
+                DrawerLayout.LOCK_MODE_UNLOCKED, mDrawerLayout.getDrawerLockMode(mEndDrawer));
+
+        // Lock the start drawer open
+        onView(withId(R.id.drawer_layout)).perform(
+                setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN, mStartDrawer));
+        // And check that it's locked open (with the end drawer unlocked)
+        assertEquals("Start drawer is now locked open",
+                DrawerLayout.LOCK_MODE_LOCKED_OPEN, mDrawerLayout.getDrawerLockMode(mStartDrawer));
+        assertEquals("End drawer is still unlocked",
+                DrawerLayout.LOCK_MODE_UNLOCKED, mDrawerLayout.getDrawerLockMode(mEndDrawer));
+
+        // Unlock the start drawer and close it
+        onView(withId(R.id.drawer_layout)).perform(
+                setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, mStartDrawer));
+        onView(withId(R.id.drawer_layout)).perform(closeDrawer(mStartDrawer));
+        // And check that both drawers are unlocked
+        assertEquals("Start drawer is now unlocked",
+                DrawerLayout.LOCK_MODE_UNLOCKED, mDrawerLayout.getDrawerLockMode(mStartDrawer));
+        assertEquals("End drawer is now unlocked",
+                DrawerLayout.LOCK_MODE_UNLOCKED, mDrawerLayout.getDrawerLockMode(mEndDrawer));
+
+        // Lock the end drawer open
+        onView(withId(R.id.drawer_layout)).perform(
+                setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN, mEndDrawer));
+        // And check that it's locked open (with the start drawer unlocked)
+        assertEquals("Start drawer is still unlocked",
+                DrawerLayout.LOCK_MODE_UNLOCKED, mDrawerLayout.getDrawerLockMode(mStartDrawer));
+        assertEquals("End drawer is now locked open",
+                DrawerLayout.LOCK_MODE_LOCKED_OPEN, mDrawerLayout.getDrawerLockMode(mEndDrawer));
+
+        // Unlock the end drawer and close it
+        onView(withId(R.id.drawer_layout)).perform(
+                setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, mEndDrawer));
+        onView(withId(R.id.drawer_layout)).perform(closeDrawer(mEndDrawer));
+        // And check that both drawers are unlocked
+        assertEquals("Start drawer is now unlocked",
+                DrawerLayout.LOCK_MODE_UNLOCKED, mDrawerLayout.getDrawerLockMode(mStartDrawer));
+        assertEquals("End drawer is now unlocked",
+                DrawerLayout.LOCK_MODE_UNLOCKED, mDrawerLayout.getDrawerLockMode(mEndDrawer));
+    }
+
+    @Test
+    @SmallTest
+    public void testDrawerLockUnlockLtr() {
+        onView(withId(R.id.drawer_layout)).perform(
+                setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_LTR));
+
+        verifyDrawerLockUnlock();
+    }
+
+    @Test
+    @SmallTest
+    public void testDrawerLockUnlockRtl() {
+        onView(withId(R.id.drawer_layout)).perform(
+                setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_RTL));
+
+        verifyDrawerLockUnlock();
+    }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutTest.java b/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutTest.java
index c5cc561..f0aef76 100755
--- a/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutTest.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutTest.java
@@ -15,23 +15,26 @@
  */
 package android.support.v7.app;
 
-import android.content.res.Resources;
-import android.os.Build;
-import android.view.View;
+import org.junit.Before;
+import org.junit.Test;
 
-import android.support.test.espresso.action.Press;
+import android.os.Build;
 import android.support.test.espresso.action.GeneralLocation;
 import android.support.test.espresso.action.GeneralSwipeAction;
+import android.support.test.espresso.action.Press;
 import android.support.test.espresso.action.Swipe;
-
 import android.support.v4.view.GravityCompat;
-import android.support.v7.custom.CustomDrawerLayout;
 import android.support.v7.appcompat.test.R;
-
-import org.junit.Test;
+import android.support.v7.custom.CustomDrawerLayout;
+import android.view.View;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
 
 import static android.support.test.espresso.Espresso.onView;
 import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 public class DrawerLayoutTest extends BaseInstrumentationTestCase<DrawerLayoutActivity> {
     private CustomDrawerLayout mDrawerLayout;
@@ -44,10 +47,10 @@
         super(DrawerLayoutActivity.class);
     }
 
-    public void setUp() throws Exception {
-        super.setUp();
+    @Before
+    public void setUp() {
 
-        final DrawerLayoutActivity activity = getActivity();
+        final DrawerLayoutActivity activity = mActivityTestRule.getActivity();
         mDrawerLayout = (CustomDrawerLayout) activity.findViewById(R.id.drawer_layout);
         mStartDrawer = mDrawerLayout.findViewById(R.id.start_drawer);
         mContentView = mDrawerLayout.findViewById(R.id.content);
@@ -58,6 +61,7 @@
     }
 
     @Test
+    @MediumTest
     public void testDrawerOpenCloseViaAPI() {
         assertFalse("Initial state", mDrawerLayout.isDrawerOpen(GravityCompat.START));
 
@@ -73,6 +77,7 @@
     }
 
     @Test
+    @MediumTest
     public void testDrawerOpenCloseWithRedundancyViaAPI() {
         assertFalse("Initial state", mDrawerLayout.isDrawerOpen(GravityCompat.START));
 
@@ -100,6 +105,7 @@
     }
 
     @Test
+    @MediumTest
     public void testDrawerOpenCloseViaSwipes() {
         assertFalse("Initial state", mDrawerLayout.isDrawerOpen(GravityCompat.START));
 
@@ -117,6 +123,7 @@
     }
 
     @Test
+    @MediumTest
     public void testDrawerOpenCloseWithRedundancyViaSwipes() {
         assertFalse("Initial state", mDrawerLayout.isDrawerOpen(GravityCompat.START));
 
@@ -148,6 +155,7 @@
     }
 
     @Test
+    @SmallTest
     public void testDrawerHeight() {
         // Open the drawer so it becomes visible
         onView(withId(R.id.drawer_layout)).perform(
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 58a8f3f..1dcefcb 100644
--- a/v7/appcompat/tests/src/android/support/v7/app/LayoutInflaterFactoryTestCase.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/LayoutInflaterFactoryTestCase.java
@@ -35,6 +35,10 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
 public class LayoutInflaterFactoryTestCase
         extends BaseInstrumentationTestCase<LayoutInflaterFactoryTestActivity> {
 
diff --git a/v7/appcompat/tests/src/android/support/v7/custom/FitWindowsContentLayout.java b/v7/appcompat/tests/src/android/support/v7/custom/FitWindowsContentLayout.java
new file mode 100644
index 0000000..dfad1c8
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/custom/FitWindowsContentLayout.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.v7.custom;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+public class FitWindowsContentLayout extends FrameLayout {
+
+    private final Rect mInsets = new Rect();
+    private boolean mFitSystemWindowsCalled = false;
+
+    public FitWindowsContentLayout(Context context) {
+        super(context);
+    }
+
+    public FitWindowsContentLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public FitWindowsContentLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    protected boolean fitSystemWindows(Rect insets) {
+        mFitSystemWindowsCalled = true;
+        mInsets.set(insets);
+
+        return super.fitSystemWindows(insets);
+    }
+
+    public boolean getFitsSystemWindowsCalled() {
+        return mFitSystemWindowsCalled;
+    }
+
+    public Rect getFitSystemWindowsInsets() {
+        return mInsets;
+    }
+}
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 ae8e681..8bb452e 100644
--- a/v7/appcompat/tests/src/android/support/v7/testutils/TestUtils.java
+++ b/v7/appcompat/tests/src/android/support/v7/testutils/TestUtils.java
@@ -17,13 +17,15 @@
 
 package android.support.v7.testutils;
 
+import junit.framework.Assert;
+
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.drawable.Drawable;
+import android.os.SystemClock;
 import android.support.annotation.ColorInt;
 import android.support.annotation.NonNull;
-import junit.framework.Assert;
 
 public class TestUtils {
     /**
@@ -93,4 +95,10 @@
             bitmap.recycle();
         }
     }
+
+    public static void waitForActivityDestroyed(BaseTestActivity activity) {
+        while (!activity.isDestroyed()) {
+            SystemClock.sleep(30);
+        }
+    }
 }
\ No newline at end of file
diff --git a/v7/appcompat/tests/src/android/support/v7/testutils/TestUtilsActions.java b/v7/appcompat/tests/src/android/support/v7/testutils/TestUtilsActions.java
new file mode 100644
index 0000000..53971b2
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/testutils/TestUtilsActions.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.v7.testutils;
+
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.support.v4.view.ViewCompat;
+import android.view.View;
+import org.hamcrest.Matcher;
+
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+
+public class TestUtilsActions {
+    /**
+     * 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();
+            }
+        };
+    }
+}
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 b517fc3..2ae4d64 100644
--- a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatBaseViewTest.java
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatBaseViewTest.java
@@ -24,17 +24,20 @@
 import android.support.annotation.NonNull;
 import android.support.v4.content.res.ResourcesCompat;
 import android.support.v4.graphics.ColorUtils;
+import android.support.v7.app.BaseInstrumentationTestCase;
 import android.support.v7.appcompat.test.R;
 import android.support.v7.testutils.AppCompatTintableViewActions;
 import android.support.v7.testutils.BaseTestActivity;
 import android.support.v7.testutils.TestUtils;
-import android.test.ActivityInstrumentationTestCase2;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.view.View;
 import android.view.ViewGroup;
+import org.junit.Before;
+import org.junit.Test;
 
 import static android.support.test.espresso.Espresso.onView;
 import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static org.junit.Assert.assertNull;
 
 /**
  * Base class for testing custom view extensions in appcompat-v7 that implement the
@@ -43,19 +46,20 @@
  * base view class (such as <code>AppCompatTextView</code>'s all-caps support).
  */
 public abstract class AppCompatBaseViewTest<A extends BaseTestActivity, T extends View>
-        extends ActivityInstrumentationTestCase2<A> {
+        extends BaseInstrumentationTestCase<A> {
     protected ViewGroup mContainer;
 
+    protected Resources mResources;
+
     public AppCompatBaseViewTest(Class clazz) {
         super(clazz);
     }
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        final A activity = getActivity();
+    @Before
+    public void setUp() {
+        final A activity = mActivityTestRule.getActivity();
         mContainer = (ViewGroup) activity.findViewById(R.id.container);
+        mResources = activity.getResources();
     }
 
     private void verifyBackgroundIsColoredAs(String description, @NonNull View view,
@@ -70,10 +74,10 @@
      * This method tests that background tinting is not applied when the
      * tintable view has no background.
      */
+    @Test
     @SmallTest
     public void testBackgroundTintingWithNoBackground() {
         final @IdRes int viewId = R.id.view_tinted_no_background;
-        final Resources res = getActivity().getResources();
         final T view = (T) mContainer.findViewById(viewId);
 
         // Note that all the asserts in this test check that the view background
@@ -93,7 +97,7 @@
         // Load a new color state list, set it on the view and check that the background
         // is still null.
         final ColorStateList sandColor = ResourcesCompat.getColorStateList(
-                res, R.color.color_state_list_sand, null);
+                mResources, R.color.color_state_list_sand, null);
         onView(withId(viewId)).perform(
                 AppCompatTintableViewActions.setBackgroundTintList(sandColor));
 
@@ -111,18 +115,24 @@
      * in enabled and disabled state across a number of <code>ColorStateList</code>s set as
      * background tint lists on the same background.
      */
+    @Test
     @SmallTest
     public void testBackgroundTintingAcrossStateChange() {
         final @IdRes int viewId = R.id.view_tinted_background;
-        final Resources res = getActivity().getResources();
         final T view = (T) mContainer.findViewById(viewId);
 
-        @ColorInt int lilacDefault = ResourcesCompat.getColor(res, R.color.lilac_default, null);
-        @ColorInt int lilacDisabled = ResourcesCompat.getColor(res, R.color.lilac_disabled, null);
-        @ColorInt int sandDefault = ResourcesCompat.getColor(res, R.color.sand_default, null);
-        @ColorInt int sandDisabled = ResourcesCompat.getColor(res, R.color.sand_disabled, null);
-        @ColorInt int oceanDefault = ResourcesCompat.getColor(res, R.color.ocean_default, null);
-        @ColorInt int oceanDisabled = ResourcesCompat.getColor(res, R.color.ocean_disabled, null);
+        final @ColorInt int lilacDefault = ResourcesCompat.getColor(
+                mResources, R.color.lilac_default, null);
+        final @ColorInt int lilacDisabled = ResourcesCompat.getColor(
+                mResources, R.color.lilac_disabled, null);
+        final @ColorInt int sandDefault = ResourcesCompat.getColor(
+                mResources, R.color.sand_default, null);
+        final @ColorInt int sandDisabled = ResourcesCompat.getColor(
+                mResources, R.color.sand_disabled, null);
+        final @ColorInt int oceanDefault = ResourcesCompat.getColor(
+                mResources, R.color.ocean_default, null);
+        final @ColorInt int oceanDisabled = ResourcesCompat.getColor(
+                mResources, R.color.ocean_disabled, null);
 
         // Test the default state for tinting set up in the layout XML file.
         verifyBackgroundIsColoredAs("Default lilac tinting in enabled state", view,
@@ -143,7 +153,7 @@
         // Load a new color state list, set it on the view and check that the background has
         // switched to the matching entry in newly set color state list.
         final ColorStateList sandColor = ResourcesCompat.getColorStateList(
-                res, R.color.color_state_list_sand, null);
+                mResources, R.color.color_state_list_sand, null);
         onView(withId(viewId)).perform(
                 AppCompatTintableViewActions.setBackgroundTintList(sandColor));
         verifyBackgroundIsColoredAs("New sand tinting in enabled state", view,
@@ -164,7 +174,7 @@
         // Load another color state list, set it on the view and check that the background has
         // switched to the matching entry in newly set color state list.
         final ColorStateList oceanColor = ResourcesCompat.getColorStateList(
-                res, R.color.color_state_list_ocean, null);
+                mResources, R.color.color_state_list_ocean, null);
         onView(withId(viewId)).perform(
                 AppCompatTintableViewActions.setBackgroundTintList(oceanColor));
         verifyBackgroundIsColoredAs("New ocean tinting in enabled state", view,
@@ -188,20 +198,20 @@
      * in enabled and disabled state across the same background respects the currently set
      * background tinting mode.
      */
+    @Test
     @SmallTest
     public void testBackgroundTintingAcrossModeChange() {
         final @IdRes int viewId = R.id.view_untinted_background;
-        final Resources res = getActivity().getResources();
         final T view = (T) mContainer.findViewById(viewId);
 
-        @ColorInt int emeraldDefault = ResourcesCompat.getColor(
-                res, R.color.emerald_translucent_default, null);
-        @ColorInt int emeraldDisabled = ResourcesCompat.getColor(
-                res, R.color.emerald_translucent_disabled, null);
+        final @ColorInt int emeraldDefault = ResourcesCompat.getColor(
+                mResources, R.color.emerald_translucent_default, null);
+        final @ColorInt int emeraldDisabled = ResourcesCompat.getColor(
+                mResources, R.color.emerald_translucent_disabled, null);
         // This is the fill color of R.drawable.test_background_green set on our view
         // that we'll be testing in this method
-        @ColorInt int backgroundColor = ResourcesCompat.getColor(
-                res, R.color.test_green, null);
+        final @ColorInt int backgroundColor = ResourcesCompat.getColor(
+                mResources, R.color.test_green, null);
 
         // Test the default state for tinting set up in the layout XML file.
         verifyBackgroundIsColoredAs("Default no tinting in enabled state", view,
@@ -221,7 +231,7 @@
         // Load a new color state list, set it on the view and check that the background has
         // switched to the matching entry in newly set color state list.
         final ColorStateList emeraldColor = ResourcesCompat.getColorStateList(
-                res, R.color.color_state_list_emerald_translucent, null);
+                mResources, R.color.color_state_list_emerald_translucent, null);
         onView(withId(viewId)).perform(
                 AppCompatTintableViewActions.setBackgroundTintList(emeraldColor));
         verifyBackgroundIsColoredAs("New emerald tinting in enabled state under src_in", view,
@@ -259,20 +269,22 @@
      * This method tests that opaque background tinting applied to tintable view
      * is applied correctly after changing the background itself of the view.
      */
+    @Test
     @SmallTest
     public void testBackgroundOpaqueTintingAcrossBackgroundChange() {
         final @IdRes int viewId = R.id.view_tinted_no_background;
-        final Resources res = getActivity().getResources();
         final T view = (T) mContainer.findViewById(viewId);
 
-        @ColorInt int lilacDefault = ResourcesCompat.getColor(res, R.color.lilac_default, null);
-        @ColorInt int lilacDisabled = ResourcesCompat.getColor(res, R.color.lilac_disabled, null);
+        final @ColorInt int lilacDefault = ResourcesCompat.getColor(
+                mResources, R.color.lilac_default, null);
+        final @ColorInt int lilacDisabled = ResourcesCompat.getColor(
+                mResources, R.color.lilac_disabled, null);
 
         assertNull("No background after XML loading", view.getBackground());
 
         // Set background on our view
         onView(withId(viewId)).perform(AppCompatTintableViewActions.setBackgroundDrawable(
-                ResourcesCompat.getDrawable(res, R.drawable.test_background_green, null)));
+                ResourcesCompat.getDrawable(mResources, R.drawable.test_background_green, null)));
 
         // Test the default state for tinting set up in the layout XML file.
         verifyBackgroundIsColoredAs("Default lilac tinting in enabled state on green background",
@@ -315,22 +327,22 @@
      * This method tests that translucent background tinting applied to tintable view
      * is applied correctly after changing the background itself of the view.
      */
+    @Test
     @SmallTest
     public void testBackgroundTranslucentTintingAcrossBackgroundChange() {
         final @IdRes int viewId = R.id.view_untinted_no_background;
-        final Resources res = getActivity().getResources();
         final T view = (T) mContainer.findViewById(viewId);
 
-        @ColorInt int emeraldDefault = ResourcesCompat.getColor(
-                res, R.color.emerald_translucent_default, null);
-        @ColorInt int emeraldDisabled = ResourcesCompat.getColor(
-                res, R.color.emerald_translucent_disabled, null);
+        final @ColorInt int emeraldDefault = ResourcesCompat.getColor(
+                mResources, R.color.emerald_translucent_default, null);
+        final @ColorInt int emeraldDisabled = ResourcesCompat.getColor(
+                mResources, R.color.emerald_translucent_disabled, null);
         // This is the fill color of R.drawable.test_background_green set on our view
         // that we'll be testing in this method
-        @ColorInt int backgroundColorGreen = ResourcesCompat.getColor(
-                res, R.color.test_green, null);
-        @ColorInt int backgroundColorRed = ResourcesCompat.getColor(
-                res, R.color.test_red, null);
+        final @ColorInt int backgroundColorGreen = ResourcesCompat.getColor(
+                mResources, R.color.test_green, null);
+        final @ColorInt int backgroundColorRed = ResourcesCompat.getColor(
+                mResources, R.color.test_red, null);
 
         assertNull("No background after XML loading", view.getBackground());
 
@@ -342,13 +354,13 @@
                 AppCompatTintableViewActions.setBackgroundTintMode(PorterDuff.Mode.SRC_OVER));
         // Load and set a translucent color state list as the background tint list
         final ColorStateList emeraldColor = ResourcesCompat.getColorStateList(
-                res, R.color.color_state_list_emerald_translucent, null);
+                mResources, R.color.color_state_list_emerald_translucent, null);
         onView(withId(viewId)).perform(
                 AppCompatTintableViewActions.setBackgroundTintList(emeraldColor));
 
         // Set background on our view
         onView(withId(viewId)).perform(AppCompatTintableViewActions.setBackgroundDrawable(
-                ResourcesCompat.getDrawable(res, R.drawable.test_background_green, null)));
+                ResourcesCompat.getDrawable(mResources, R.drawable.test_background_green, null)));
 
         // From this point on in this method we're allowing a margin of error in checking the
         // color of the view background. This is due to both translucent colors being used
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 d2abe95..8d6233d 100644
--- a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatImageViewTest.java
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatImageViewTest.java
@@ -15,14 +15,6 @@
  */
 package android.support.v7.widget;
 
-import android.content.res.Resources;
-import android.support.v7.appcompat.test.R;
-import android.support.v7.testutils.AppCompatTextViewActions;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import static android.support.test.espresso.Espresso.onView;
-import static android.support.test.espresso.matcher.ViewMatchers.withId;
-
 /**
  * In addition to all tinting-related tests done by the base class, this class provides
  * tests specific to <code>AppCompatImageView</code> class.
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 6448e7d..e2c6edf 100644
--- a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewTest.java
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewTest.java
@@ -15,13 +15,14 @@
  */
 package android.support.v7.widget;
 
-import android.content.res.Resources;
 import android.support.v7.appcompat.test.R;
 import android.support.v7.testutils.AppCompatTextViewActions;
 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 org.junit.Assert.assertEquals;
 
 /**
  * In addition to all tinting-related tests done by the base class, this class provides
@@ -33,11 +34,11 @@
         super(AppCompatTextViewActivity.class);
     }
 
+    @Test
     @SmallTest
     public void testAllCaps() throws Throwable {
-        final Resources res = getActivity().getResources();
-        final String text1 = res.getString(R.string.sample_text1);
-        final String text2 = res.getString(R.string.sample_text2);
+        final String text1 = mResources.getString(R.string.sample_text1);
+        final String text2 = mResources.getString(R.string.sample_text2);
 
         final AppCompatTextView textView1 =
                 (AppCompatTextView) mContainer.findViewById(R.id.text_view_caps1);
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 14e1bfe..9072100 100644
--- a/v7/appcompat/tests/src/android/support/v7/widget/ListPopupWindowTest.java
+++ b/v7/appcompat/tests/src/android/support/v7/widget/ListPopupWindowTest.java
@@ -18,8 +18,9 @@
 import android.app.Instrumentation;
 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.ActivityInstrumentationTestCase2;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
@@ -32,17 +33,22 @@
 import android.widget.PopupWindow;
 import android.widget.TextView;
 
+import org.junit.Before;
+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.withDecorView;
-import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
-import static android.support.test.espresso.matcher.ViewMatchers.withId;
-import static android.support.test.espresso.matcher.ViewMatchers.withText;
+import static android.support.test.espresso.matcher.ViewMatchers.*;
 import static org.hamcrest.core.Is.is;
 import static org.hamcrest.core.IsNot.not;
+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 class ListPopupWindowTest extends ActivityInstrumentationTestCase2<PopupTestActivity> {
+public class ListPopupWindowTest extends BaseInstrumentationTestCase<PopupTestActivity> {
     private FrameLayout mContainer;
 
     private Button mButton;
@@ -61,15 +67,14 @@
         super(PopupTestActivity.class);
     }
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        final PopupTestActivity activity = getActivity();
+    @Before
+    public void setUp() throws Exception {
+        final PopupTestActivity activity = mActivityTestRule.getActivity();
         mContainer = (FrameLayout) activity.findViewById(R.id.container);
         mButton = (Button) mContainer.findViewById(R.id.test_button);
     }
 
+    @Test
     @SmallTest
     public void testBasicContent() {
         Builder popupBuilder = new Builder();
@@ -79,7 +84,7 @@
         assertNotNull("Popup window created", mListPopupWindow);
         assertTrue("Popup window showing", mListPopupWindow.isShowing());
 
-        final View mainDecorView = getActivity().getWindow().getDecorView();
+        final View mainDecorView = mActivityTestRule.getActivity().getWindow().getDecorView();
         onView(withText("Alice"))
                 .inRoot(withDecorView(not(is(mainDecorView))))
                 .check(matches(isDisplayed()));
@@ -97,6 +102,7 @@
                 .check(matches(isDisplayed()));
     }
 
+    @Test
     @SmallTest
     public void testAnchoring() {
         Builder popupBuilder = new Builder();
@@ -121,15 +127,16 @@
                 popupOnScreenXY[1] + rect.top);
     }
 
+    @Test
     @SmallTest
-    public void testDismissalViaAPI() throws Throwable {
+    public void testDismissalViaAPI() {
         Builder popupBuilder = new Builder().withDismissListener();
         popupBuilder.wireToActionButton();
 
         onView(withId(R.id.test_button)).perform(click());
         assertTrue("Popup window showing", mListPopupWindow.isShowing());
 
-        runTestOnUiThread(new Runnable() {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
             @Override
             public void run() {
                 mListPopupWindow.dismiss();
@@ -178,7 +185,7 @@
         // of view or data. Also, we don't want to use View.dispatchTouchEvent directly as
         // that would require emulation of two separate sequences as well.
 
-        Instrumentation instrumentation = getInstrumentation();
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
 
         // Inject DOWN event
         long downTime = SystemClock.uptimeMillis();
@@ -211,16 +218,19 @@
         assertEquals("Click on underlying container", !setupAsModal, mIsContainerClicked);
     }
 
+    @Test
     @SmallTest
     public void testDismissalOutsideNonModal() throws Throwable {
         testDismissalViaTouch(false);
     }
 
+    @Test
     @SmallTest
     public void testDismissalOutsideModal() throws Throwable {
         testDismissalViaTouch(true);
     }
 
+    @Test
     @SmallTest
     public void testItemClickViaEvent() {
         Builder popupBuilder = new Builder().withItemClickListener();
@@ -231,16 +241,18 @@
 
         assertEquals("Clicked item before click", -1, mListPopupClickedItem);
 
+        final View mainDecorView = mActivityTestRule.getActivity().getWindow().getDecorView();
         onView(withText("Charlie"))
-                .inRoot(withDecorView(not(is(getActivity().getWindow().getDecorView()))))
+                .inRoot(withDecorView(not(is(mainDecorView))))
                 .perform(click());
         assertEquals("Clicked item after click", 2, mListPopupClickedItem);
         // Our item click listener also dismisses the popup
         assertFalse("Popup window not showing after click", mListPopupWindow.isShowing());
     }
 
+    @Test
     @SmallTest
-    public void testItemClickViaAPI() throws Throwable {
+    public void testItemClickViaAPI() {
         Builder popupBuilder = new Builder().withItemClickListener();
         popupBuilder.wireToActionButton();
 
@@ -248,7 +260,7 @@
         assertTrue("Popup window showing", mListPopupWindow.isShowing());
 
         assertEquals("Clicked item before click", -1, mListPopupClickedItem);
-        runTestOnUiThread(new Runnable() {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
             @Override
             public void run() {
                 mListPopupWindow.performItemClick(1);
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/PopupMenuTest.java b/v7/appcompat/tests/src/android/support/v7/widget/PopupMenuTest.java
new file mode 100644
index 0000000..4218775
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/widget/PopupMenuTest.java
@@ -0,0 +1,578 @@
+/*
+ * 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.test.InstrumentationRegistry;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.Matchers;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.Before;
+import org.junit.Test;
+
+import android.app.Instrumentation;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.SystemClock;
+import android.support.test.espresso.Root;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.support.v7.app.BaseInstrumentationTestCase;
+import android.support.v7.appcompat.test.R;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewParent;
+import android.widget.Button;
+import android.widget.FrameLayout;
+
+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.doesNotExist;
+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.RootMatchers.withDecorView;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.withClassName;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static android.support.test.espresso.matcher.ViewMatchers.withText;
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+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
+    // 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";
+    private FrameLayout mContainer;
+
+    private Button mButton;
+
+    private PopupMenu mPopupMenu;
+
+    private int mPopupClickedMenuItemId = -1;
+
+    private boolean mIsDismissedCalled = false;
+
+    private Resources mResources;
+
+    private View mMainDecorView;
+
+    public PopupMenuTest() {
+        super(PopupTestActivity.class);
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        final PopupTestActivity activity = mActivityTestRule.getActivity();
+        mContainer = (FrameLayout) activity.findViewById(R.id.container);
+        mButton = (Button) mContainer.findViewById(R.id.test_button);
+        mResources = mActivityTestRule.getActivity().getResources();
+        mMainDecorView = mActivityTestRule.getActivity().getWindow().getDecorView();
+    }
+
+    @Test
+    @SmallTest
+    public void testBasicContent() {
+        final Builder menuBuilder = new Builder();
+        menuBuilder.wireToActionButton();
+
+        onView(withId(R.id.test_button)).perform(click());
+        assertNotNull("Popup menu created", mPopupMenu);
+        // Unlike ListPopupWindow, PopupMenu doesn't have an API to check whether it is showing.
+        // Use a custom matcher to check the visibility of the drop down list view instead.
+        onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME)))
+                .inRoot(isPlatformPopup()).check(matches(isDisplayed()));
+
+        // Note that MenuItem.isVisible() refers to the current "data" visibility state
+        // and not the "on screen" visibility state. This is why we're testing the display
+        // visibility of our main and sub menu items.
+
+        onView(withText(mResources.getString(R.string.popup_menu_highlight)))
+                .inRoot(withDecorView(not(is(mMainDecorView))))
+                .check(matches(isDisplayed()));
+        onView(withText(mResources.getString(R.string.popup_menu_edit)))
+                .inRoot(withDecorView(not(is(mMainDecorView))))
+                .check(matches(isDisplayed()));
+        onView(withText(mResources.getString(R.string.popup_menu_delete)))
+                .inRoot(withDecorView(not(is(mMainDecorView))))
+                .check(matches(isDisplayed()));
+        onView(withText(mResources.getString(R.string.popup_menu_ignore)))
+                .inRoot(withDecorView(not(is(mMainDecorView))))
+                .check(matches(isDisplayed()));
+        onView(withText(mResources.getString(R.string.popup_menu_share)))
+                .inRoot(withDecorView(not(is(mMainDecorView))))
+                .check(matches(isDisplayed()));
+        onView(withText(mResources.getString(R.string.popup_menu_print)))
+                .inRoot(withDecorView(not(is(mMainDecorView))))
+                .check(matches(isDisplayed()));
+
+        // Share submenu items should not be visible
+        onView(withText(mResources.getString(R.string.popup_menu_share_email)))
+                .inRoot(withDecorView(not(is(mMainDecorView))))
+                .check(doesNotExist());
+        onView(withText(mResources.getString(R.string.popup_menu_share_circles)))
+                .inRoot(withDecorView(not(is(mMainDecorView))))
+                .check(doesNotExist());
+    }
+
+    /**
+     * Returns the location of our popup menu in its window.
+     */
+    private int[] getPopupLocationInWindow() {
+        final int[] location = new int[2];
+        onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME)))
+                .inRoot(isPlatformPopup()).perform(new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isDisplayed();
+            }
+
+            @Override
+            public String getDescription() {
+                return "Popup matcher";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                view.getLocationInWindow(location);
+            }
+        });
+        return location;
+    }
+
+    /**
+     * Returns the location of our popup menu on the screen.
+     */
+    private int[] getPopupLocationOnScreen() {
+        final int[] location = new int[2];
+        onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME)))
+                .inRoot(isPlatformPopup()).perform(new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isDisplayed();
+            }
+
+            @Override
+            public String getDescription() {
+                return "Popup matcher";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                view.getLocationOnScreen(location);
+            }
+        });
+        return location;
+    }
+
+    /**
+     * Returns the combined padding around the content of our popup menu.
+     */
+    private Rect getPopupPadding() {
+        final Rect result = new Rect();
+        onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME)))
+                .inRoot(isPlatformPopup()).perform(new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isDisplayed();
+            }
+
+            @Override
+            public String getDescription() {
+                return "Popup matcher";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                // Traverse the parent hierarchy and combine all their paddings
+                result.setEmpty();
+                final Rect current = new Rect();
+                while (true) {
+                    ViewParent parent = view.getParent();
+                    if (parent == null || !(parent instanceof View)) {
+                        return;
+                    }
+
+                    view = (View) parent;
+                    Drawable currentBackground = view.getBackground();
+                    if (currentBackground != null) {
+                        currentBackground.getPadding(current);
+                        result.left += current.left;
+                        result.right += current.right;
+                        result.top += current.top;
+                        result.bottom += current.bottom;
+                    }
+                }
+            }
+        });
+        return result;
+    }
+
+    /**
+     * Returns a root matcher that matches roots that have window focus on their decor view.
+     */
+    private static Matcher<Root> hasWindowFocus() {
+        return new TypeSafeMatcher<Root>() {
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("has window focus");
+            }
+
+            @Override
+            public boolean matchesSafely(Root root) {
+                View rootView = root.getDecorView();
+                return rootView.hasWindowFocus();
+            }
+        };
+    }
+
+    @Test
+    @SmallTest
+    public void testAnchoring() {
+        Builder menuBuilder = new Builder();
+        menuBuilder.wireToActionButton();
+
+        onView(withId(R.id.test_button)).perform(click());
+
+        final int[] anchorOnScreenXY = new int[2];
+        final int[] popupOnScreenXY = getPopupLocationOnScreen();
+        final int[] popupInWindowXY = getPopupLocationInWindow();
+        final Rect popupPadding = getPopupPadding();
+
+        mButton.getLocationOnScreen(anchorOnScreenXY);
+
+        // Allow for off-by-one mismatch in anchoring
+        assertEquals("Anchoring X", anchorOnScreenXY[0] + popupInWindowXY[0],
+                popupOnScreenXY[0], 1);
+        assertEquals("Anchoring Y", anchorOnScreenXY[1] + popupInWindowXY[1] + mButton.getHeight(),
+                popupOnScreenXY[1] + popupPadding.top, 1);
+    }
+
+    @Test
+    @SmallTest
+    public void testDismissalViaAPI() {
+        Builder menuBuilder = new Builder().withDismissListener();
+        menuBuilder.wireToActionButton();
+
+        onView(withId(R.id.test_button)).perform(click());
+
+        // Since PopupMenu is not a View, we can't use Espresso's view actions to invoke
+        // the dismiss() API
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mPopupMenu.dismiss();
+            }
+        });
+
+        assertTrue("Dismiss listener called", mIsDismissedCalled);
+        // Unlike ListPopupWindow, PopupMenu doesn't have an API to check whether it is showing.
+        // Use a custom matcher to check the visibility of the drop down list view instead.
+        onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))).check(doesNotExist());
+    }
+
+    @Test
+    @SmallTest
+    public void testDismissalViaTouch() throws Throwable {
+        Builder menuBuilder = new Builder().withDismissListener();
+        menuBuilder.wireToActionButton();
+
+        onView(withId(R.id.test_button)).perform(click());
+
+        // Determine the location of the popup on the screen so that we can emulate
+        // a tap outside of its bounds to dismiss it
+        final int[] popupOnScreenXY = getPopupLocationOnScreen();
+        final Rect popupPadding = getPopupPadding();
+
+
+        int emulatedTapX = popupOnScreenXY[0] - popupPadding.left - 20;
+        int emulatedTapY = popupOnScreenXY[1] + popupPadding.top + 20;
+
+        // The logic below uses Instrumentation to emulate a tap outside the bounds of the
+        // displayed popup menu. This tap is then treated by the framework to be "split" as
+        // the ACTION_OUTSIDE for the popup itself, as well as DOWN / MOVE / UP for the underlying
+        // view root if the popup is not modal.
+        // It is not correct to emulate these two sequences separately in the test, as it
+        // wouldn't emulate the user-facing interaction for this test. Note that usage
+        // of Instrumentation is necessary here since Espresso's actions operate at the level
+        // of view or data. Also, we don't want to use View.dispatchTouchEvent directly as
+        // that would require emulation of two separate sequences as well.
+
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+
+        // Inject DOWN event
+        long downTime = SystemClock.uptimeMillis();
+        MotionEvent eventDown = MotionEvent.obtain(
+                downTime, downTime, MotionEvent.ACTION_DOWN, emulatedTapX, emulatedTapY, 1);
+        instrumentation.sendPointerSync(eventDown);
+
+        // Inject MOVE event
+        long moveTime = SystemClock.uptimeMillis();
+        MotionEvent eventMove = MotionEvent.obtain(
+                moveTime, moveTime, MotionEvent.ACTION_MOVE, emulatedTapX, emulatedTapY, 1);
+        instrumentation.sendPointerSync(eventMove);
+
+        // Inject UP event
+        long upTime = SystemClock.uptimeMillis();
+        MotionEvent eventUp = MotionEvent.obtain(
+                upTime, upTime, MotionEvent.ACTION_UP, emulatedTapX, emulatedTapY, 1);
+        instrumentation.sendPointerSync(eventUp);
+
+        // Wait for the system to process all events in the queue
+        instrumentation.waitForIdleSync();
+
+        // At this point our popup should not be showing and should have notified its
+        // dismiss listener
+        assertTrue("Dismiss listener called", mIsDismissedCalled);
+        onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))).check(doesNotExist());
+    }
+
+    @Test
+    @SmallTest
+    public void testSimpleMenuItemClickViaEvent() {
+        Builder menuBuilder = new Builder().withMenuItemClickListener();
+        menuBuilder.wireToActionButton();
+
+        onView(withId(R.id.test_button)).perform(click());
+
+        assertEquals("Clicked item before click", -1, mPopupClickedMenuItemId);
+
+        onView(withText(mResources.getString(R.string.popup_menu_delete)))
+                .inRoot(withDecorView(not(is(mMainDecorView))))
+                .perform(click());
+        assertEquals("Clicked item after click", R.id.action_delete, mPopupClickedMenuItemId);
+
+        // Popup menu should be automatically dismissed on selecting an item
+        onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))).check(doesNotExist());
+    }
+
+    @Test
+    @SmallTest
+    public void testSimpleMenuItemClickViaAPI() {
+        Builder menuBuilder = new Builder().withMenuItemClickListener();
+        menuBuilder.wireToActionButton();
+
+        onView(withId(R.id.test_button)).perform(click());
+
+        assertEquals("Clicked item before click", -1, mPopupClickedMenuItemId);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mPopupMenu.getMenu().performIdentifierAction(R.id.action_highlight, 0);
+            }
+        });
+
+        assertEquals("Clicked item after click", R.id.action_highlight, mPopupClickedMenuItemId);
+
+        // Popup menu should be automatically dismissed on selecting an item
+        onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))).check(doesNotExist());
+    }
+
+    @Test
+    @SmallTest
+    public void testSubMenuClicksViaEvent() throws Throwable {
+        Builder menuBuilder = new Builder().withMenuItemClickListener();
+        menuBuilder.wireToActionButton();
+
+        onView(withId(R.id.test_button)).perform(click());
+
+        assertEquals("Clicked item before click", -1, mPopupClickedMenuItemId);
+
+        onView(withText(mResources.getString(R.string.popup_menu_share)))
+                .inRoot(withDecorView(not(is(mMainDecorView))))
+                .perform(click());
+        assertEquals("Clicked item after click", R.id.action_share, mPopupClickedMenuItemId);
+
+        // Sleep for a bit to allow the menu -> submenu transition to complete
+        Thread.sleep(1000);
+
+        // At this point we should now have our sub-menu displayed. At this point on newer
+        // platform versions (L+) we have two view roots on the screen - one for the main popup
+        // menu and one for the submenu that has just been activated. If we only use the
+        // logic based on decor view, Espresso will time out on waiting for the first root
+        // to acquire window focus. This is why from this point on in this test we are using
+        // two root conditions to detect the submenu - one with decor view not being the same
+        // as the decor view of our main activity window, and the other that checks for window
+        // focus.
+
+        // Unlike ListPopupWindow, PopupMenu doesn't have an API to check whether it is showing.
+        // Use a custom matcher to check the visibility of the drop down list view instead.
+        onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME)))
+                .inRoot(allOf(withDecorView(not(is(mMainDecorView))), hasWindowFocus()))
+                .check(matches(isDisplayed()));
+
+        // Note that MenuItem.isVisible() refers to the current "data" visibility state
+        // and not the "on screen" visibility state. This is why we're testing the display
+        // visibility of our main and sub menu items.
+
+        // Share submenu items should now be visible
+        onView(withText(mResources.getString(R.string.popup_menu_share_email)))
+                .inRoot(allOf(withDecorView(not(is(mMainDecorView))), hasWindowFocus()))
+                .check(matches(isDisplayed()));
+        onView(withText(mResources.getString(R.string.popup_menu_share_circles)))
+                .inRoot(allOf(withDecorView(not(is(mMainDecorView))), hasWindowFocus()))
+                .check(matches(isDisplayed()));
+
+        // Now click an item in the sub-menu
+        onView(withText(mResources.getString(R.string.popup_menu_share_circles)))
+                .inRoot(allOf(withDecorView(not(is(mMainDecorView))), hasWindowFocus()))
+                .perform(click());
+        assertEquals("Clicked submenu item after click", R.id.action_share_circles,
+                mPopupClickedMenuItemId);
+
+        // Popup menu should be automatically dismissed on selecting an item in the submenu
+        onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))).check(doesNotExist());
+    }
+
+    @Test
+    @SmallTest
+    public void testSubMenuClicksViaAPI() throws Throwable {
+        Builder menuBuilder = new Builder().withMenuItemClickListener();
+        menuBuilder.wireToActionButton();
+
+        onView(withId(R.id.test_button)).perform(click());
+
+        assertEquals("Clicked item before click", -1, mPopupClickedMenuItemId);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mPopupMenu.getMenu().performIdentifierAction(R.id.action_share, 0);
+            }
+        });
+
+        assertEquals("Clicked item after click", R.id.action_share, mPopupClickedMenuItemId);
+
+        // Sleep for a bit to allow the menu -> submenu transition to complete
+        Thread.sleep(1000);
+
+        // At this point we should now have our sub-menu displayed. At this point on newer
+        // platform versions (L+) we have two view roots on the screen - one for the main popup
+        // menu and one for the submenu that has just been activated. If we only use the
+        // logic based on decor view, Espresso will time out on waiting for the first root
+        // to acquire window focus. This is why from this point on in this test we are using
+        // two root conditions to detect the submenu - one with decor view not being the same
+        // as the decor view of our main activity window, and the other that checks for window
+        // focus.
+
+        // Unlike ListPopupWindow, PopupMenu doesn't have an API to check whether it is showing.
+        // Use a custom matcher to check the visibility of the drop down list view instead.
+        onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME)))
+                .inRoot(allOf(withDecorView(not(is(mMainDecorView))), hasWindowFocus()))
+                .check(matches(isDisplayed()));
+
+        // Note that MenuItem.isVisible() refers to the current "data" visibility state
+        // and not the "on screen" visibility state. This is why we're testing the display
+        // visibility of our main and sub menu items.
+
+        // Share submenu items should now be visible
+        onView(withText(mResources.getString(R.string.popup_menu_share_email)))
+                .inRoot(allOf(withDecorView(not(is(mMainDecorView))), hasWindowFocus()))
+                .check(matches(isDisplayed()));
+        onView(withText(mResources.getString(R.string.popup_menu_share_circles)))
+                .inRoot(allOf(withDecorView(not(is(mMainDecorView))), hasWindowFocus()))
+                .check(matches(isDisplayed()));
+
+        // Now ask the share submenu to perform an action on its specific menu item
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mPopupMenu.getMenu().findItem(R.id.action_share).getSubMenu().
+                        performIdentifierAction(R.id.action_share_email, 0);
+            }
+        });
+        assertEquals("Clicked submenu item after click", R.id.action_share_email,
+                mPopupClickedMenuItemId);
+
+        // Popup menu should be automatically dismissed on selecting an item in the submenu
+        onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))).check(doesNotExist());
+    }
+
+    /**
+     * Inner helper class to configure an instance of <code>PopupMenu</code> for the
+     * specific test. The main reason for its existence is that once a popup menu is shown
+     * with the show() method, most of its configuration APIs are no-ops. This means that
+     * we can't add logic that is specific to a certain test once it's shown and we have a
+     * reference to a displayed PopupMenu.
+     */
+    private class Builder {
+        private boolean mHasDismissListener;
+        private boolean mHasMenuItemClickListener;
+
+        public Builder() {
+        }
+
+        public Builder withMenuItemClickListener() {
+            mHasMenuItemClickListener = true;
+            return this;
+        }
+
+        public Builder withDismissListener() {
+            mHasDismissListener = true;
+            return this;
+        }
+
+        private void show() {
+            mPopupMenu = new PopupMenu(mContainer.getContext(), mButton);
+            final MenuInflater menuInflater = mPopupMenu.getMenuInflater();
+            menuInflater.inflate(R.menu.popup_menu, mPopupMenu.getMenu());
+
+            if (mHasMenuItemClickListener) {
+                // Register a listener to be notified when a menu item in our popup menu has
+                // been clicked.
+                mPopupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
+                    @Override
+                    public boolean onMenuItemClick(MenuItem item) {
+                        mPopupClickedMenuItemId = item.getItemId();
+                        return true;
+                    }
+                });
+            }
+
+            if (mHasDismissListener) {
+                // Register a listener to be notified when our popup menu is dismissed.
+                mPopupMenu.setOnDismissListener(new PopupMenu.OnDismissListener() {
+                    @Override
+                    public void onDismiss(PopupMenu menu) {
+                        mIsDismissedCalled = true;
+                    }
+                });
+            }
+
+            // Show the popup menu
+            mPopupMenu.show();
+        }
+
+        public void wireToActionButton() {
+            mButton.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    show();
+                }
+            });
+        }
+    }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/PopupTestActivity.java b/v7/appcompat/tests/src/android/support/v7/widget/PopupTestActivity.java
index f7ddba5..9a3736a 100644
--- a/v7/appcompat/tests/src/android/support/v7/widget/PopupTestActivity.java
+++ b/v7/appcompat/tests/src/android/support/v7/widget/PopupTestActivity.java
@@ -18,6 +18,10 @@
 import android.support.v7.appcompat.test.R;
 import android.support.v7.testutils.BaseTestActivity;
 
+/**
+ * This activity is used to test both {@link ListPopupWindow} and {@link PopupMenu} classes.
+ *
+ */
 public class PopupTestActivity extends BaseTestActivity {
     @Override
     protected int getContentViewLayoutResId() {
diff --git a/v7/cardview/build.gradle b/v7/cardview/build.gradle
index 143519c..8636474 100644
--- a/v7/cardview/build.gradle
+++ b/v7/cardview/build.gradle
@@ -1,10 +1,10 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
 
 archivesBaseName = 'cardview-v7'
 
 android {
     // WARNING: should be 7
-    compileSdkVersion 'current'
+    compileSdkVersion project.ext.currentSdk
 
     defaultConfig {
         minSdkVersion 7
@@ -12,6 +12,11 @@
         //targetSdkVersion 19
     }
 
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_7
+        targetCompatibility JavaVersion.VERSION_1_7
+    }
+
     sourceSets {
         main.manifest.srcFile 'AndroidManifest.xml'
         main.java.srcDirs = ['base', 'eclair-mr1', 'jellybean-mr1', 'api21', 'src']
diff --git a/v7/gridlayout/build.gradle b/v7/gridlayout/build.gradle
index 9e55c01..6e363dd 100644
--- a/v7/gridlayout/build.gradle
+++ b/v7/gridlayout/build.gradle
@@ -1,4 +1,4 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
 
 archivesBaseName = 'gridlayout-v7'
 
@@ -7,7 +7,7 @@
 }
 
 android {
-    compileSdkVersion 'current'
+    compileSdkVersion project.ext.currentSdk
 
     sourceSets {
         main.manifest.srcFile 'AndroidManifest.xml'
@@ -25,6 +25,11 @@
         androidTest.manifest.srcFile 'tests/AndroidManifest.xml'
     }
 
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_7
+        targetCompatibility JavaVersion.VERSION_1_7
+    }
+
     lintOptions {
         // TODO: fix errors and reenable.
         abortOnError false
diff --git a/v7/mediarouter/api/current.txt b/v7/mediarouter/api/current.txt
index d082642..75b0fce 100644
--- a/v7/mediarouter/api/current.txt
+++ b/v7/mediarouter/api/current.txt
@@ -565,6 +565,7 @@
     method public void onRouteRemoved(android.support.v7.media.MediaRouter, android.support.v7.media.MediaRouter.RouteInfo);
     method public void onRouteSelected(android.support.v7.media.MediaRouter, android.support.v7.media.MediaRouter.RouteInfo);
     method public void onRouteUnselected(android.support.v7.media.MediaRouter, android.support.v7.media.MediaRouter.RouteInfo);
+    method public void onRouteUnselected(android.support.v7.media.MediaRouter, android.support.v7.media.MediaRouter.RouteInfo, int);
     method public void onRouteVolumeChanged(android.support.v7.media.MediaRouter, android.support.v7.media.MediaRouter.RouteInfo);
   }
 
diff --git a/v7/mediarouter/build.gradle b/v7/mediarouter/build.gradle
index 48f4750..37224f0 100644
--- a/v7/mediarouter/build.gradle
+++ b/v7/mediarouter/build.gradle
@@ -1,4 +1,4 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
 
 archivesBaseName = 'mediarouter-v7'
 
@@ -41,7 +41,7 @@
 }
 
 android {
-    compileSdkVersion 'current'
+    compileSdkVersion project.ext.currentSdk
 
     sourceSets {
         main.manifest.srcFile 'AndroidManifest.xml'
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouter.java b/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
index 8926e53..20d784f 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
@@ -78,12 +78,14 @@
 
     /**
      * Passed to {@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect(int)}
-     * when the reason the route was unselected is unknown.
+     * and {@link Callback#onRouteUnselected(MediaRouter, RouteInfo, int)} when the reason the route
+     * was unselected is unknown.
      */
     public static final int UNSELECT_REASON_UNKNOWN = 0;
     /**
      * Passed to {@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect(int)}
-     * when the user pressed the disconnect button to disconnect and keep playing.
+     * and {@link Callback#onRouteUnselected(MediaRouter, RouteInfo, int)} when the user pressed
+     * the disconnect button to disconnect and keep playing.
      * <p>
      *
      * @see {@link MediaRouteDescriptor#canDisconnectAndKeepPlaying()}.
@@ -91,12 +93,14 @@
     public static final int UNSELECT_REASON_DISCONNECTED = 1;
     /**
      * Passed to {@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect(int)}
-     * when the user pressed the stop casting button.
+     * and {@link Callback#onRouteUnselected(MediaRouter, RouteInfo, int)} when the user pressed
+     * the stop casting button.
      */
     public static final int UNSELECT_REASON_STOPPED = 2;
     /**
      * Passed to {@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect(int)}
-     * when the user selected a different route.
+     * and {@link Callback#onRouteUnselected(MediaRouter, RouteInfo, int)} when the user selected
+     * a different route.
      */
     public static final int UNSELECT_REASON_ROUTE_CHANGED = 3;
 
@@ -1712,6 +1716,8 @@
 
         /**
          * Called when the supplied media route becomes unselected as the active route.
+         * For detailed reason, override {@link #onRouteUnselected(MediaRouter, RouteInfo, int)}
+         * instead.
          *
          * @param router The media router reporting the event.
          * @param route The route that has been unselected.
@@ -1720,6 +1726,26 @@
         }
 
         /**
+         * Called when the supplied media route becomes unselected as the active route.
+         * The default implementation calls {@link #onRouteUnselected}.
+         * <p>
+         * The reason provided will be one of the following:
+         * <ul>
+         * <li>{@link MediaRouter#UNSELECT_REASON_UNKNOWN}</li>
+         * <li>{@link MediaRouter#UNSELECT_REASON_DISCONNECTED}</li>
+         * <li>{@link MediaRouter#UNSELECT_REASON_STOPPED}</li>
+         * <li>{@link MediaRouter#UNSELECT_REASON_ROUTE_CHANGED}</li>
+         * </ul>
+         *
+         * @param router The media router reporting the event.
+         * @param route The route that has been unselected.
+         * @param reason The reason for unselecting the route.
+         */
+        public void onRouteUnselected(MediaRouter router, RouteInfo route, int reason) {
+            onRouteUnselected(router, route);
+        }
+
+        /**
          * Called when a media route has been added.
          *
          * @param router The media router reporting the event.
@@ -2444,7 +2470,8 @@
                         Log.d(TAG, "Route unselected: " + mSelectedRoute + " reason: "
                                 + unselectReason);
                     }
-                    mCallbackHandler.post(CallbackHandler.MSG_ROUTE_UNSELECTED, mSelectedRoute);
+                    mCallbackHandler.post(CallbackHandler.MSG_ROUTE_UNSELECTED, mSelectedRoute,
+                            unselectReason);
                     if (mSelectedRouteController != null) {
                         mSelectedRouteController.onUnselect(unselectReason);
                         mSelectedRouteController.onRelease();
@@ -2733,10 +2760,17 @@
                 obtainMessage(msg, obj).sendToTarget();
             }
 
+            public void post(int msg, Object obj, int arg) {
+                Message message = obtainMessage(msg, obj);
+                message.arg1 = arg;
+                message.sendToTarget();
+            }
+
             @Override
             public void handleMessage(Message msg) {
                 final int what = msg.what;
                 final Object obj = msg.obj;
+                final int arg = msg.arg1;
 
                 // Synchronize state with the system media router.
                 syncWithSystemProvider(what, obj);
@@ -2756,7 +2790,7 @@
 
                     final int callbackCount = mTempCallbackRecords.size();
                     for (int i = 0; i < callbackCount; i++) {
-                        invokeCallback(mTempCallbackRecords.get(i), what, obj);
+                        invokeCallback(mTempCallbackRecords.get(i), what, obj, arg);
                     }
                 } finally {
                     mTempCallbackRecords.clear();
@@ -2780,7 +2814,7 @@
                 }
             }
 
-            private void invokeCallback(CallbackRecord record, int what, Object obj) {
+            private void invokeCallback(CallbackRecord record, int what, Object obj, int arg) {
                 final MediaRouter router = record.mRouter;
                 final MediaRouter.Callback callback = record.mCallback;
                 switch (what & MSG_TYPE_MASK) {
@@ -2809,7 +2843,7 @@
                                 callback.onRouteSelected(router, route);
                                 break;
                             case MSG_ROUTE_UNSELECTED:
-                                callback.onRouteUnselected(router, route);
+                                callback.onRouteUnselected(router, route, arg);
                                 break;
                         }
                         break;
diff --git a/v7/palette/build.gradle b/v7/palette/build.gradle
index 8c2885b..65e298d 100644
--- a/v7/palette/build.gradle
+++ b/v7/palette/build.gradle
@@ -1,4 +1,4 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
 
 archivesBaseName = 'palette-v7'
 
diff --git a/v7/preference/build.gradle b/v7/preference/build.gradle
index a0681ed..45b4f96 100644
--- a/v7/preference/build.gradle
+++ b/v7/preference/build.gradle
@@ -14,7 +14,7 @@
  * limitations under the License
  */
 
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
 
 archivesBaseName = 'preference-v7'
 
@@ -25,7 +25,7 @@
 }
 
 android {
-    compileSdkVersion 'current'
+    compileSdkVersion project.ext.currentSdk
 
     sourceSets {
         main.manifest.srcFile 'AndroidManifest.xml'
diff --git a/v7/recyclerview/api/current.txt b/v7/recyclerview/api/current.txt
index d74c0e5..504e05d 100644
--- a/v7/recyclerview/api/current.txt
+++ b/v7/recyclerview/api/current.txt
@@ -235,6 +235,8 @@
     method public abstract int getEnd();
     method public abstract int getEndAfterPadding();
     method public abstract int getEndPadding();
+    method public abstract int getMode();
+    method public abstract int getModeInOther();
     method public abstract int getStartAfterPadding();
     method public abstract int getTotalSpace();
     method public int getTotalSpaceChange();
@@ -459,6 +461,7 @@
     method public boolean canScrollHorizontally();
     method public boolean canScrollVertically();
     method public boolean checkLayoutParams(android.support.v7.widget.RecyclerView.LayoutParams);
+    method public static int chooseSize(int, int, int);
     method public int computeHorizontalScrollExtent(android.support.v7.widget.RecyclerView.State);
     method public int computeHorizontalScrollOffset(android.support.v7.widget.RecyclerView.State);
     method public int computeHorizontalScrollRange(android.support.v7.widget.RecyclerView.State);
@@ -480,7 +483,8 @@
     method public int getBottomDecorationHeight(android.view.View);
     method public android.view.View getChildAt(int);
     method public int getChildCount();
-    method public static int getChildMeasureSpec(int, int, int, boolean);
+    method public static deprecated int getChildMeasureSpec(int, int, int, boolean);
+    method public static int getChildMeasureSpec(int, int, int, int, boolean);
     method public boolean getClipToPadding();
     method public int getColumnCountForAccessibility(android.support.v7.widget.RecyclerView.Recycler, android.support.v7.widget.RecyclerView.State);
     method public int getDecoratedBottom(android.view.View);
@@ -491,6 +495,7 @@
     method public int getDecoratedTop(android.view.View);
     method public android.view.View getFocusedChild();
     method public int getHeight();
+    method public int getHeightMode();
     method public int getItemCount();
     method public int getItemViewType(android.view.View);
     method public int getLayoutDirection();
@@ -510,11 +515,14 @@
     method public int getSelectionModeForAccessibility(android.support.v7.widget.RecyclerView.Recycler, android.support.v7.widget.RecyclerView.State);
     method public int getTopDecorationHeight(android.view.View);
     method public int getWidth();
+    method public int getWidthMode();
     method public boolean hasFocus();
     method public void ignoreView(android.view.View);
     method public boolean isAttachedToWindow();
+    method public boolean isAutoMeasureEnabled();
     method public boolean isFocused();
     method public boolean isLayoutHierarchical(android.support.v7.widget.RecyclerView.Recycler, android.support.v7.widget.RecyclerView.State);
+    method public boolean isMeasurementCacheEnabled();
     method public boolean isSmoothScrolling();
     method public void layoutDecorated(android.view.View, int, int, int, int);
     method public void measureChild(android.view.View, int, int);
@@ -563,7 +571,10 @@
     method public int scrollHorizontallyBy(int, android.support.v7.widget.RecyclerView.Recycler, android.support.v7.widget.RecyclerView.State);
     method public void scrollToPosition(int);
     method public int scrollVerticallyBy(int, android.support.v7.widget.RecyclerView.Recycler, android.support.v7.widget.RecyclerView.State);
+    method public void setAutoMeasureEnabled(boolean);
+    method public void setMeasuredDimension(android.graphics.Rect, int, int);
     method public void setMeasuredDimension(int, int);
+    method public void setMeasurementCacheEnabled(boolean);
     method public void smoothScrollToPosition(android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.State, int);
     method public void startSmoothScroll(android.support.v7.widget.RecyclerView.SmoothScroller);
     method public void stopIgnoringView(android.view.View);
@@ -684,6 +695,7 @@
     method public int getItemCount();
     method public int getTargetScrollPosition();
     method public boolean hasTargetScrollPosition();
+    method public boolean isMeasuring();
     method public boolean isPreLayout();
     method public void put(int, java.lang.Object);
     method public void remove(int);
diff --git a/v7/recyclerview/build.gradle b/v7/recyclerview/build.gradle
index 3e368a1..386694d 100644
--- a/v7/recyclerview/build.gradle
+++ b/v7/recyclerview/build.gradle
@@ -1,4 +1,4 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
 
 archivesBaseName = 'recyclerview-v7'
 
@@ -37,6 +37,11 @@
         androidTest.manifest.srcFile 'tests/AndroidManifest.xml'
     }
 
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_7
+        targetCompatibility JavaVersion.VERSION_1_7
+    }
+
     lintOptions {
         // TODO: fix errors and reenable.
         abortOnError false
diff --git a/v7/recyclerview/jvm-tests/src/android/support/v7/widget/AdapterHelperTest.java b/v7/recyclerview/jvm-tests/src/android/support/v7/widget/AdapterHelperTest.java
index 09f7336..a5a8e33 100644
--- a/v7/recyclerview/jvm-tests/src/android/support/v7/widget/AdapterHelperTest.java
+++ b/v7/recyclerview/jvm-tests/src/android/support/v7/widget/AdapterHelperTest.java
@@ -246,6 +246,18 @@
     }
 
     @Test
+    public void testNotifyAfterPre() {
+        setupBasic(10, 2, 3);
+        add(2, 1);
+        mAdapterHelper.preProcess();
+        add(3, 1);
+        mAdapterHelper.consumeUpdatesInOnePass();
+        mPreProcessClone.applyOps(mFirstPassUpdates, mTestAdapter);
+        mPreProcessClone.applyOps(mSecondPassUpdates, mTestAdapter);
+        assertAdaptersEqual(mTestAdapter, mPreProcessClone);
+    }
+
+    @Test
     public void testSinglePass() {
         setupBasic(10, 2, 3);
         add(2, 1);
diff --git a/v7/recyclerview/jvm-tests/src/android/support/v7/widget/ViewInfoStoreTest.java b/v7/recyclerview/jvm-tests/src/android/support/v7/widget/ViewInfoStoreTest.java
index f460f79..c776f61 100644
--- a/v7/recyclerview/jvm-tests/src/android/support/v7/widget/ViewInfoStoreTest.java
+++ b/v7/recyclerview/jvm-tests/src/android/support/v7/widget/ViewInfoStoreTest.java
@@ -54,6 +54,37 @@
     }
 
     @Test
+    public void addOverridePre() {
+        RecyclerView.ViewHolder vh = new MockViewHolder();
+        MockInfo info = new MockInfo();
+        mStore.addToPreLayout(vh, info);
+        MockInfo info2 = new MockInfo();
+        mStore.addToPreLayout(vh, info2);
+        assertSame(info2, find(vh, FLAG_PRE));
+    }
+
+    @Test
+    public void addOverridePost() {
+        RecyclerView.ViewHolder vh = new MockViewHolder();
+        MockInfo info = new MockInfo();
+        mStore.addToPostLayout(vh, info);
+        MockInfo info2 = new MockInfo();
+        mStore.addToPostLayout(vh, info2);
+        assertSame(info2, find(vh, FLAG_POST));
+    }
+
+    @Test
+    public void addRemoveAndReAdd() {
+        RecyclerView.ViewHolder vh = new MockViewHolder();
+        MockInfo pre = new MockInfo();
+        mStore.addToPreLayout(vh, pre);
+        MockInfo post1 = new MockInfo();
+        mStore.addToPostLayout(vh, post1);
+        mStore.onViewDetached(vh);
+        mStore.addToDisappearedInLayout(vh);
+    }
+
+    @Test
     public void addToPreLayout() {
         RecyclerView.ViewHolder vh = new MockViewHolder();
         MockInfo info = new MockInfo();
diff --git a/v7/recyclerview/src/android/support/v7/widget/AdapterHelper.java b/v7/recyclerview/src/android/support/v7/widget/AdapterHelper.java
index 9220c5e..b1dd91c 100644
--- a/v7/recyclerview/src/android/support/v7/widget/AdapterHelper.java
+++ b/v7/recyclerview/src/android/support/v7/widget/AdapterHelper.java
@@ -530,7 +530,7 @@
      */
     boolean onItemRangeMoved(int from, int to, int itemCount) {
         if (from == to) {
-            return false;//no-op
+            return false; // no-op
         }
         if (itemCount != 1) {
             throw new IllegalArgumentException("Moving more than 1 item is not supported yet");
@@ -612,6 +612,10 @@
         return position;
     }
 
+    boolean hasUpdates() {
+        return !mPostponedList.isEmpty() && !mPendingUpdates.isEmpty();
+    }
+
     /**
      * Queued operation to happen when child views are updated.
      */
diff --git a/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java
index e182c93..62416dc 100644
--- a/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java
@@ -38,11 +38,6 @@
     private static final String TAG = "GridLayoutManager";
     public static final int DEFAULT_SPAN_COUNT = -1;
     /**
-     * The measure spec for the scroll direction.
-     */
-    static final int MAIN_DIR_SPEC =
-            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
-    /**
      * Span size have been changed but we've not done a new layout calculation.
      */
     boolean mPendingSpanCountChange = false;
@@ -221,8 +216,13 @@
 
     @Override
     public RecyclerView.LayoutParams generateDefaultLayoutParams() {
-        return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
-                ViewGroup.LayoutParams.WRAP_CONTENT);
+        if (mOrientation == HORIZONTAL) {
+            return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+                    ViewGroup.LayoutParams.FILL_PARENT);
+        } else {
+            return new LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
+                    ViewGroup.LayoutParams.WRAP_CONTENT);
+        }
     }
 
     @Override
@@ -273,35 +273,70 @@
         calculateItemBorders(totalSpace);
     }
 
-    private void calculateItemBorders(int totalSpace) {
-        if (mCachedBorders == null || mCachedBorders.length != mSpanCount + 1
-                || mCachedBorders[mCachedBorders.length - 1] != totalSpace) {
-            mCachedBorders = new int[mSpanCount + 1];
+    @Override
+    public void setMeasuredDimension(Rect childrenBounds, int wSpec, int hSpec) {
+        if (mCachedBorders == null) {
+            super.setMeasuredDimension(childrenBounds, wSpec, hSpec);
         }
-        mCachedBorders[0] = 0;
-        int sizePerSpan = totalSpace / mSpanCount;
-        int sizePerSpanRemainder = totalSpace % mSpanCount;
+        final int width, height;
+        if (mOrientation == VERTICAL) {
+            int usedHeight = childrenBounds.height() + getPaddingTop() + getPaddingBottom();
+            height = chooseSize(hSpec, usedHeight, getMinimumHeight());
+            width = chooseSize(wSpec, mCachedBorders[mCachedBorders.length - 1],
+                    getMinimumWidth());
+        } else {
+            int usedWidth = childrenBounds.width() + getPaddingLeft() + getPaddingRight();
+            width = chooseSize(wSpec, usedWidth, getMinimumWidth());
+            height = chooseSize(hSpec, mCachedBorders[mCachedBorders.length - 1],
+                    getMinimumHeight());
+        }
+        setMeasuredDimension(width, height);
+    }
+
+    /**
+     * @param totalSpace Total available space after padding is removed
+     */
+    private void calculateItemBorders(int totalSpace) {
+        mCachedBorders = calculateItemBorders(mCachedBorders, mSpanCount, totalSpace);
+    }
+
+    /**
+     * @param cachedBorders The out array
+     * @param spanCount number of spans
+     * @param totalSpace total available space after padding is removed
+     * @return The updated array. Might be the same instance as the provided array if its size
+     * has not changed.
+     */
+    static int[] calculateItemBorders(int[] cachedBorders, int spanCount, int totalSpace) {
+        if (cachedBorders == null || cachedBorders.length != spanCount + 1
+                || cachedBorders[cachedBorders.length - 1] != totalSpace) {
+            cachedBorders = new int[spanCount + 1];
+        }
+        cachedBorders[0] = 0;
+        int sizePerSpan = totalSpace / spanCount;
+        int sizePerSpanRemainder = totalSpace % spanCount;
         int consumedPixels = 0;
         int additionalSize = 0;
-        for (int i = 1; i <= mSpanCount; i++) {
+        for (int i = 1; i <= spanCount; i++) {
             int itemSize = sizePerSpan;
             additionalSize += sizePerSpanRemainder;
-            if (additionalSize > 0 && (mSpanCount - additionalSize) < sizePerSpanRemainder) {
+            if (additionalSize > 0 && (spanCount - additionalSize) < sizePerSpanRemainder) {
                 itemSize += 1;
-                additionalSize -= mSpanCount;
+                additionalSize -= spanCount;
             }
             consumedPixels += itemSize;
-            mCachedBorders[i] = consumedPixels;
+            cachedBorders[i] = consumedPixels;
         }
+        return cachedBorders;
     }
 
     @Override
     void onAnchorReady(RecyclerView.Recycler recycler, RecyclerView.State state,
-                       AnchorInfo anchorInfo) {
-        super.onAnchorReady(recycler, state, anchorInfo);
+                       AnchorInfo anchorInfo, int itemDirection) {
+        super.onAnchorReady(recycler, state, anchorInfo, itemDirection);
         updateMeasurements();
         if (state.getItemCount() > 0 && !state.isPreLayout()) {
-            ensureAnchorIsInFirstSpan(recycler, state, anchorInfo);
+            ensureAnchorIsInCorrectSpan(recycler, state, anchorInfo, itemDirection);
         }
         ensureViewSet();
     }
@@ -328,12 +363,32 @@
         return super.scrollVerticallyBy(dy, recycler, state);
     }
 
-    private void ensureAnchorIsInFirstSpan(RecyclerView.Recycler recycler, RecyclerView.State state,
-                                           AnchorInfo anchorInfo) {
+    private void ensureAnchorIsInCorrectSpan(RecyclerView.Recycler recycler,
+            RecyclerView.State state, AnchorInfo anchorInfo, int itemDirection) {
+        final boolean layingOutInPrimaryDirection =
+                itemDirection == LayoutState.ITEM_DIRECTION_TAIL;
         int span = getSpanIndex(recycler, state, anchorInfo.mPosition);
-        while (span > 0 && anchorInfo.mPosition > 0) {
-            anchorInfo.mPosition--;
-            span = getSpanIndex(recycler, state, anchorInfo.mPosition);
+        if (layingOutInPrimaryDirection) {
+            // choose span 0
+            while (span > 0 && anchorInfo.mPosition > 0) {
+                anchorInfo.mPosition--;
+                span = getSpanIndex(recycler, state, anchorInfo.mPosition);
+            }
+        } else {
+            // choose the max span we can get. hopefully last one
+            final int indexLimit = state.getItemCount() - 1;
+            int pos = anchorInfo.mPosition;
+            int bestSpan = span;
+            while (pos < indexLimit) {
+                int next = getSpanIndex(recycler, state, pos + 1);
+                if (next > bestSpan) {
+                    pos += 1;
+                    bestSpan = next;
+                } else {
+                    break;
+                }
+            }
+            anchorInfo.mPosition = pos;
         }
     }
 
@@ -433,6 +488,15 @@
     @Override
     void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
             LayoutState layoutState, LayoutChunkResult result) {
+        final int otherDirSpecMode = mOrientationHelper.getModeInOther();
+        final boolean flexibleInOtherDir = otherDirSpecMode != View.MeasureSpec.EXACTLY;
+        final int currentOtherDirSize = getChildCount() > 0 ? mCachedBorders[mSpanCount] : 0;
+        // if grid layout's dimensions are not specified, let the new row change the measurements
+        // This is not perfect since we not covering all rows but still solves an important case
+        // where they may have a header row which should be laid out according to children.
+        if (flexibleInOtherDir) {
+            updateMeasurements(); //  reset measurements
+        }
         final boolean layingOutInPrimaryDirection =
                 layoutState.mItemDirection == LayoutState.ITEM_DIRECTION_TAIL;
         int count = 0;
@@ -470,6 +534,7 @@
         }
 
         int maxSize = 0;
+        float maxSizeInOther = 0; // use a float to get size per span
 
         // we should assign spans before item decor offsets are calculated
         assignSpans(recycler, state, count, consumedSpanCount, layingOutInPrimaryDirection);
@@ -490,35 +555,68 @@
             }
 
             final LayoutParams lp = (LayoutParams) view.getLayoutParams();
-            final int spec = View.MeasureSpec.makeMeasureSpec(
-                    mCachedBorders[lp.mSpanIndex + lp.mSpanSize] -
-                            mCachedBorders[lp.mSpanIndex],
-                    View.MeasureSpec.EXACTLY);
+            final int spec = getChildMeasureSpec(mCachedBorders[lp.mSpanIndex + lp.mSpanSize] -
+                    mCachedBorders[lp.mSpanIndex], otherDirSpecMode, 0,
+                    mOrientation == HORIZONTAL ? lp.height : lp.width,
+                    false);
+            final int mainSpec = getChildMeasureSpec(mOrientationHelper.getTotalSpace(),
+                    mOrientationHelper.getMode(), 0,
+                    mOrientation == VERTICAL ? lp.height : lp.width, true);
             if (mOrientation == VERTICAL) {
-                measureChildWithDecorationsAndMargin(view, spec, getMainDirSpec(lp.height), false);
+                measureChildWithDecorationsAndMargin(view, spec, mainSpec, false, false);
             } else {
-                measureChildWithDecorationsAndMargin(view, getMainDirSpec(lp.width), spec, false);
+                measureChildWithDecorationsAndMargin(view, mainSpec, spec, false, false);
             }
             final int size = mOrientationHelper.getDecoratedMeasurement(view);
             if (size > maxSize) {
                 maxSize = size;
             }
+            final float otherSize = 1f * mOrientationHelper.getDecoratedMeasurementInOther(view) /
+                    lp.mSpanSize;
+            if (otherSize > maxSizeInOther) {
+                maxSizeInOther = otherSize;
+            }
         }
-
-        // views that did not measure the maxSize has to be re-measured
-        final int maxMeasureSpec = getMainDirSpec(maxSize);
+        if (flexibleInOtherDir) {
+            // re-distribute columns
+            guessMeasurement(maxSizeInOther, currentOtherDirSize);
+            // now we should re-measure any item that was match parent.
+            maxSize = 0;
+            for (int i = 0; i < count; i++) {
+                View view = mSet[i];
+                final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+                final int spec = getChildMeasureSpec(mCachedBorders[lp.mSpanIndex + lp.mSpanSize] -
+                                mCachedBorders[lp.mSpanIndex], View.MeasureSpec.EXACTLY, 0,
+                        mOrientation == HORIZONTAL ? lp.height : lp.width, false);
+                final int mainSpec = getChildMeasureSpec(mOrientationHelper.getTotalSpace(),
+                        mOrientationHelper.getMode(), 0,
+                        mOrientation == VERTICAL ? lp.height : lp.width, true);
+                if (mOrientation == VERTICAL) {
+                    measureChildWithDecorationsAndMargin(view, spec, mainSpec, false, true);
+                } else {
+                    measureChildWithDecorationsAndMargin(view, mainSpec, spec, false, true);
+                }
+                final int size = mOrientationHelper.getDecoratedMeasurement(view);
+                if (size > maxSize) {
+                    maxSize = size;
+                }
+            }
+        }
+        // Views that did not measure the maxSize has to be re-measured
+        // We will stop doing this once we introduce Gravity in the GLM layout params
+        final int maxMeasureSpec = View.MeasureSpec.makeMeasureSpec(maxSize,
+                View.MeasureSpec.EXACTLY);
         for (int i = 0; i < count; i ++) {
             final View view = mSet[i];
             if (mOrientationHelper.getDecoratedMeasurement(view) != maxSize) {
                 final LayoutParams lp = (LayoutParams) view.getLayoutParams();
-                final int spec = View.MeasureSpec.makeMeasureSpec(
-                        mCachedBorders[lp.mSpanIndex + lp.mSpanSize] -
-                                mCachedBorders[lp.mSpanIndex],
-                        View.MeasureSpec.EXACTLY);
+                final int spec = getChildMeasureSpec(mCachedBorders[lp.mSpanIndex + lp.mSpanSize]
+                                - mCachedBorders[lp.mSpanIndex], View.MeasureSpec.EXACTLY, 0,
+                        mOrientation == HORIZONTAL ? lp.height : lp.width, false);
                 if (mOrientation == VERTICAL) {
-                    measureChildWithDecorationsAndMargin(view, spec, maxMeasureSpec, true);
+                    measureChildWithDecorationsAndMargin(view, spec, maxMeasureSpec, true, true);
                 } else {
-                    measureChildWithDecorationsAndMargin(view, maxMeasureSpec, spec, true);
+                    measureChildWithDecorationsAndMargin(view, maxMeasureSpec, spec, true, true);
                 }
             }
         }
@@ -547,8 +645,13 @@
             View view = mSet[i];
             LayoutParams params = (LayoutParams) view.getLayoutParams();
             if (mOrientation == VERTICAL) {
-                left = getPaddingLeft() + mCachedBorders[params.mSpanIndex];
-                right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
+                if (isLayoutRTL()) {
+                    right = getPaddingLeft() + mCachedBorders[params.mSpanIndex + params.mSpanSize];
+                    left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
+                } else {
+                    left = getPaddingLeft() + mCachedBorders[params.mSpanIndex];
+                    right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
+                }
             } else {
                 top = getPaddingTop() + mCachedBorders[params.mSpanIndex];
                 bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);
@@ -572,16 +675,24 @@
         Arrays.fill(mSet, null);
     }
 
-    private int getMainDirSpec(int dim) {
-        if (dim < 0) {
-            return MAIN_DIR_SPEC;
-        } else {
-            return View.MeasureSpec.makeMeasureSpec(dim, View.MeasureSpec.EXACTLY);
-        }
+    /**
+     * This is called after laying out a row (if vertical) or a column (if horizontal) when the
+     * RecyclerView does not have exact measurement specs.
+     * <p>
+     * Here we try to assign a best guess width or height and re-do the layout to update other
+     * views that wanted to FILL_PARENT in the non-scroll orientation.
+     *
+     * @param maxSizeInOther The maximum size per span ratio from the measurement of the children.
+     * @param currentOtherDirSize The size before this layout chunk. There is no reason to go below.
+     */
+    private void guessMeasurement(float maxSizeInOther, int currentOtherDirSize) {
+        final int contentSize = Math.round(maxSizeInOther * mSpanCount);
+        // always re-calculate because borders were stretched during the fill
+        calculateItemBorders(Math.max(contentSize, currentOtherDirSize));
     }
 
     private void measureChildWithDecorationsAndMargin(View child, int widthSpec, int heightSpec,
-            boolean capBothSpecs) {
+            boolean capBothSpecs, boolean alreadyMeasured) {
         calculateItemDecorationsForChild(child, mDecorInsets);
         RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
         if (capBothSpecs || mOrientation == VERTICAL) {
@@ -592,7 +703,16 @@
             heightSpec = updateSpecWithExtra(heightSpec, lp.topMargin + mDecorInsets.top,
                     lp.bottomMargin + mDecorInsets.bottom);
         }
-        child.measure(widthSpec, heightSpec);
+        final boolean measure;
+        if (alreadyMeasured) {
+            measure = shouldReMeasureChild(child, widthSpec, heightSpec, lp);
+        } else {
+            measure = shouldMeasureChild(child, widthSpec, heightSpec, lp);
+        }
+        if (measure) {
+            child.measure(widthSpec, heightSpec);
+        }
+
     }
 
     private int updateSpecWithExtra(int spec, int startInset, int endInset) {
diff --git a/v7/recyclerview/src/android/support/v7/widget/LayoutState.java b/v7/recyclerview/src/android/support/v7/widget/LayoutState.java
index 2402313..bf730ad 100644
--- a/v7/recyclerview/src/android/support/v7/widget/LayoutState.java
+++ b/v7/recyclerview/src/android/support/v7/widget/LayoutState.java
@@ -15,6 +15,7 @@
  */
 
 package android.support.v7.widget;
+
 import android.view.View;
 
 /**
@@ -35,8 +36,11 @@
 
     final static int ITEM_DIRECTION_TAIL = 1;
 
-    final static int SCOLLING_OFFSET_NaN = Integer.MIN_VALUE;
-
+    /**
+     * We may not want to recycle children in some cases (e.g. layout)
+     */
+    boolean mRecycle = true;
+    
     /**
      * Number of pixels that we should fill, in the layout direction.
      */
@@ -75,6 +79,11 @@
     boolean mStopInFocusable;
 
     /**
+     * If the content is not wrapped with any value
+     */
+    boolean mInfinite;
+
+    /**
      * @return true if there are more items in the data adapter
      */
     boolean hasMore(RecyclerView.State state) {
diff --git a/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
index bef7d6a..eb2d6e7 100644
--- a/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
@@ -23,13 +23,13 @@
 import android.support.v4.view.ViewCompat;
 import android.support.v4.view.accessibility.AccessibilityEventCompat;
 import android.support.v4.view.accessibility.AccessibilityRecordCompat;
-import android.util.AttributeSet;
+import android.support.v7.widget.RecyclerView.LayoutParams;
 import android.support.v7.widget.helper.ItemTouchHelper;
+import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
-import android.support.v7.widget.RecyclerView.LayoutParams;
 
 import java.util.List;
 
@@ -154,6 +154,7 @@
     public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
         setOrientation(orientation);
         setReverseLayout(reverseLayout);
+        setAutoMeasureEnabled(true);
     }
 
     /**
@@ -170,6 +171,7 @@
         setOrientation(properties.orientation);
         setReverseLayout(properties.reverseLayout);
         setStackFromEnd(properties.stackFromEnd);
+        setAutoMeasureEnabled(true);
     }
 
     /**
@@ -527,8 +529,18 @@
         }
         int startOffset;
         int endOffset;
-        onAnchorReady(recycler, state, mAnchorInfo);
+        final int firstLayoutDirection;
+        if (mAnchorInfo.mLayoutFromEnd) {
+            firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL :
+                    LayoutState.ITEM_DIRECTION_HEAD;
+        } else {
+            firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD :
+                    LayoutState.ITEM_DIRECTION_TAIL;
+        }
+
+        onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
         detachAndScrapAttachedViews(recycler);
+        mLayoutState.mInfinite = mOrientationHelper.getMode() == View.MeasureSpec.UNSPECIFIED;
         mLayoutState.mIsPreLayout = state.isPreLayout();
         if (mAnchorInfo.mLayoutFromEnd) {
             // fill towards start
@@ -621,13 +633,14 @@
     /**
      * Method called when Anchor position is decided. Extending class can setup accordingly or
      * even update anchor info if necessary.
-     *
-     * @param recycler
-     * @param state
-     * @param anchorInfo Simple data structure to keep anchor point information for the next layout
+     * @param recycler The recycler for the layout
+     * @param state The layout state
+     * @param anchorInfo The mutable POJO that keeps the position and offset.
+     * @param firstLayoutItemDirection The direction of the first layout filling in terms of adapter
+     *                                 indices.
      */
     void onAnchorReady(RecyclerView.Recycler recycler, RecyclerView.State state,
-                       AnchorInfo anchorInfo) {
+                       AnchorInfo anchorInfo, int firstLayoutItemDirection) {
     }
 
     /**
@@ -1115,9 +1128,10 @@
 
     private void updateLayoutState(int layoutDirection, int requiredSpace,
             boolean canUseExistingSpace, RecyclerView.State state) {
+        mLayoutState.mInfinite = mOrientationHelper.getMode() == View.MeasureSpec.UNSPECIFIED;
         mLayoutState.mExtra = getExtraLayoutSpace(state);
         mLayoutState.mLayoutDirection = layoutDirection;
-        int fastScrollSpace;
+        int scrollingOffset;
         if (layoutDirection == LayoutState.LAYOUT_END) {
             mLayoutState.mExtra += mOrientationHelper.getEndPadding();
             // get the first child in the direction we are going
@@ -1128,7 +1142,7 @@
             mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection;
             mLayoutState.mOffset = mOrientationHelper.getDecoratedEnd(child);
             // calculate how much we can scroll without adding new children (independent of layout)
-            fastScrollSpace = mOrientationHelper.getDecoratedEnd(child)
+            scrollingOffset = mOrientationHelper.getDecoratedEnd(child)
                     - mOrientationHelper.getEndAfterPadding();
 
         } else {
@@ -1138,14 +1152,14 @@
                     : LayoutState.ITEM_DIRECTION_HEAD;
             mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection;
             mLayoutState.mOffset = mOrientationHelper.getDecoratedStart(child);
-            fastScrollSpace = -mOrientationHelper.getDecoratedStart(child)
+            scrollingOffset = -mOrientationHelper.getDecoratedStart(child)
                     + mOrientationHelper.getStartAfterPadding();
         }
         mLayoutState.mAvailable = requiredSpace;
         if (canUseExistingSpace) {
-            mLayoutState.mAvailable -= fastScrollSpace;
+            mLayoutState.mAvailable -= scrollingOffset;
         }
-        mLayoutState.mScrollingOffset = fastScrollSpace;
+        mLayoutState.mScrollingOffset = scrollingOffset;
     }
 
     int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
@@ -1157,8 +1171,8 @@
         final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
         final int absDy = Math.abs(dy);
         updateLayoutState(layoutDirection, absDy, true, state);
-        final int freeScroll = mLayoutState.mScrollingOffset;
-        final int consumed = freeScroll + fill(recycler, mLayoutState, state, false);
+        final int consumed = mLayoutState.mScrollingOffset
+                + fill(recycler, mLayoutState, state, false);
         if (consumed < 0) {
             if (DEBUG) {
                 Log.d(TAG, "Don't have any more elements to scroll");
@@ -1294,7 +1308,7 @@
      * @see android.support.v7.widget.LinearLayoutManager.LayoutState#mLayoutDirection
      */
     private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
-        if (!layoutState.mRecycle) {
+        if (!layoutState.mRecycle || layoutState.mInfinite) {
             return;
         }
         if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
@@ -1328,7 +1342,7 @@
         }
         int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
         LayoutChunkResult layoutChunkResult = new LayoutChunkResult();
-        while (remainingSpace > 0 && layoutState.hasMore(state)) {
+        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
             layoutChunkResult.resetInternal();
             layoutChunk(recycler, state, layoutState, layoutChunkResult);
             if (layoutChunkResult.mFinished) {
@@ -1439,6 +1453,13 @@
         result.mFocusable = view.isFocusable();
     }
 
+    @Override
+    boolean shouldMeasureTwice() {
+        return getHeightMode() != View.MeasureSpec.EXACTLY
+                && getWidthMode() != View.MeasureSpec.EXACTLY
+                && hasFlexibleChildInBothOrientations();
+    }
+
     /**
      * Converts a focusDirection to orientation.
      *
@@ -1931,7 +1952,8 @@
         boolean mIsPreLayout = false;
 
         /**
-         * The most recent {@link #scrollBy(int, RecyclerView.Recycler, RecyclerView.State)} amount.
+         * The most recent {@link #scrollBy(int, RecyclerView.Recycler, RecyclerView.State)}
+         * amount.
          */
         int mLastScrollDelta;
 
@@ -1942,6 +1964,11 @@
         List<RecyclerView.ViewHolder> mScrapList = null;
 
         /**
+         * Used when there is no limit in how many views can be laid out.
+         */
+        boolean mInfinite;
+
+        /**
          * @return true if there are more items in the data adapter
          */
         boolean hasMore(RecyclerView.State state) {
diff --git a/v7/recyclerview/src/android/support/v7/widget/OrientationHelper.java b/v7/recyclerview/src/android/support/v7/widget/OrientationHelper.java
index 8ca9851..cd1f5e1 100644
--- a/v7/recyclerview/src/android/support/v7/widget/OrientationHelper.java
+++ b/v7/recyclerview/src/android/support/v7/widget/OrientationHelper.java
@@ -166,6 +166,28 @@
     public abstract int getEndPadding();
 
     /**
+     * Returns the MeasureSpec mode for the current orientation from the LayoutManager.
+     *
+     * @return The current measure spec mode.
+     *
+     * @see View.MeasureSpec
+     * @see RecyclerView.LayoutManager#getWidthMode()
+     * @see RecyclerView.LayoutManager#getHeightMode()
+     */
+    public abstract int getMode();
+
+    /**
+     * Returns the MeasureSpec mode for the perpendicular orientation from the LayoutManager.
+     *
+     * @return The current measure spec mode.
+     *
+     * @see View.MeasureSpec
+     * @see RecyclerView.LayoutManager#getWidthMode()
+     * @see RecyclerView.LayoutManager#getHeightMode()
+     */
+    public abstract int getModeInOther();
+
+    /**
      * Creates an OrientationHelper for the given LayoutManager and orientation.
      *
      * @param layoutManager LayoutManager to attach to
@@ -257,6 +279,16 @@
             public int getEndPadding() {
                 return mLayoutManager.getPaddingRight();
             }
+
+            @Override
+            public int getMode() {
+                return mLayoutManager.getWidthMode();
+            }
+
+            @Override
+            public int getModeInOther() {
+                return mLayoutManager.getHeightMode();
+            }
         };
     }
 
@@ -333,6 +365,16 @@
             public int getEndPadding() {
                 return mLayoutManager.getPaddingBottom();
             }
+
+            @Override
+            public int getMode() {
+                return mLayoutManager.getHeightMode();
+            }
+
+            @Override
+            public int getModeInOther() {
+                return mLayoutManager.getWidthMode();
+            }
         };
     }
 }
\ No newline at end of file
diff --git a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
index edf9a45..ed12712 100644
--- a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
+++ b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
@@ -16,6 +16,7 @@
 
 
 package android.support.v7.widget;
+
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.database.Observable;
@@ -31,6 +32,7 @@
 import android.support.annotation.IntDef;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
 import android.support.v4.os.TraceCompat;
 import android.support.v4.view.InputDeviceCompat;
 import android.support.v4.view.MotionEventCompat;
@@ -46,6 +48,7 @@
 import android.support.v4.widget.EdgeEffectCompat;
 import android.support.v4.widget.ScrollerCompat;
 import android.support.v7.recyclerview.R;
+import android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.SparseArray;
@@ -55,6 +58,7 @@
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
+import android.view.View.MeasureSpec;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.ViewParent;
@@ -72,7 +76,7 @@
 
 import static android.support.v7.widget.AdapterHelper.Callback;
 import static android.support.v7.widget.AdapterHelper.UpdateOp;
-import android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo;
+
 
 /**
  * A flexible view for providing a limited window into a large data set.
@@ -285,16 +289,19 @@
 
     private final Rect mTempRect = new Rect();
     private Adapter mAdapter;
-    private LayoutManager mLayout;
+    @VisibleForTesting LayoutManager mLayout;
     private RecyclerListener mRecyclerListener;
-    private final ArrayList<ItemDecoration> mItemDecorations = new ArrayList<ItemDecoration>();
+    private final ArrayList<ItemDecoration> mItemDecorations = new ArrayList<>();
     private final ArrayList<OnItemTouchListener> mOnItemTouchListeners =
-            new ArrayList<OnItemTouchListener>();
+            new ArrayList<>();
     private OnItemTouchListener mActiveOnItemTouchListener;
     private boolean mIsAttached;
     private boolean mHasFixedSize;
     private boolean mFirstLayoutComplete;
-    private boolean mEatRequestLayout;
+
+    // Counting lock to control whether we should ignore requestLayout calls from children or not.
+    private int mEatRequestLayout = 0;
+
     private boolean mLayoutRequestEaten;
     private boolean mLayoutFrozen;
     private boolean mIgnoreMotionEventTillDown;
@@ -1094,7 +1101,8 @@
                 Log.d(TAG, "after removing animated view: " + view + ", " + this);
             }
         }
-        resumeRequestLayout(false);
+        // only clear request eaten flag if we removed the view.
+        resumeRequestLayout(!removed);
         return removed;
     }
 
@@ -1674,26 +1682,42 @@
 
 
     void eatRequestLayout() {
-        if (!mEatRequestLayout) {
-            mEatRequestLayout = true;
-            if (!mLayoutFrozen) {
-                mLayoutRequestEaten = false;
-            }
+        mEatRequestLayout++;
+        if (mEatRequestLayout == 1 && !mLayoutFrozen) {
+            mLayoutRequestEaten = false;
         }
     }
 
     void resumeRequestLayout(boolean performLayoutChildren) {
-        if (mEatRequestLayout) {
+        if (mEatRequestLayout < 1) {
+            //noinspection PointlessBooleanExpression
+            if (DEBUG) {
+                throw new IllegalStateException("invalid eat request layout count");
+            }
+            mEatRequestLayout = 1;
+        }
+        if (!performLayoutChildren) {
+            // Reset the layout request eaten counter.
+            // This is necessary since eatRequest calls can be nested in which case the outher
+            // call will override the inner one.
+            // for instance:
+            // eat layout for process adapter updates
+            //   eat layout for dispatchLayout
+            //     a bunch of req layout calls arrive
+
+            mLayoutRequestEaten = false;
+        }
+        if (mEatRequestLayout == 1) {
             // when layout is frozen we should delay dispatchLayout()
             if (performLayoutChildren && mLayoutRequestEaten && !mLayoutFrozen &&
                     mLayout != null && mAdapter != null) {
                 dispatchLayout();
             }
-            mEatRequestLayout = false;
             if (!mLayoutFrozen) {
                 mLayoutRequestEaten = false;
             }
         }
+        mEatRequestLayout--;
     }
 
     /**
@@ -2551,37 +2575,73 @@
 
     @Override
     protected void onMeasure(int widthSpec, int heightSpec) {
-        if (mAdapterUpdateDuringMeasure) {
-            eatRequestLayout();
-            processAdapterUpdatesAndSetAnimationFlags();
-
-            if (mState.mRunPredictiveAnimations) {
-                // TODO: try to provide a better approach.
-                // When RV decides to run predictive animations, we need to measure in pre-layout
-                // state so that pre-layout pass results in correct layout.
-                // On the other hand, this will prevent the layout manager from resizing properly.
-                mState.mInPreLayout = true;
-            } else {
-                // consume remaining updates to provide a consistent state with the layout pass.
-                mAdapterHelper.consumeUpdatesInOnePass();
-                mState.mInPreLayout = false;
-            }
-            mAdapterUpdateDuringMeasure = false;
-            resumeRequestLayout(false);
-        }
-
-        if (mAdapter != null) {
-            mState.mItemCount = mAdapter.getItemCount();
-        } else {
-            mState.mItemCount = 0;
-        }
         if (mLayout == null) {
             defaultOnMeasure(widthSpec, heightSpec);
-        } else {
-            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
+            return;
         }
+        if (mLayout.mAutoMeasure) {
+            final int widthMode = MeasureSpec.getMode(widthSpec);
+            final int heightMode = MeasureSpec.getMode(heightSpec);
+            final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
+                    && heightMode == MeasureSpec.EXACTLY;
+            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
+            if (skipMeasure || mAdapter == null) {
+                return;
+            }
+            if (mState.mLayoutStep == State.STEP_START) {
+                dispatchLayoutStep1();
+            }
+            // set dimensions in 2nd step. Pre-layout should happen with old dimensions for
+            // consistency
+            mLayout.setMeasureSpecs(widthSpec, heightSpec);
+            mState.mIsMeasuring = true;
+            dispatchLayoutStep2();
 
-        mState.mInPreLayout = false; // clear
+            // now we can get the width and height from the children.
+            mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
+
+            // if RecyclerView has non-exact width and height and if there is at least one child
+            // which also has non-exact width & height, we have to re-measure.
+            if (mLayout.shouldMeasureTwice()) {
+                mLayout.setMeasureSpecs(
+                        MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
+                        MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
+                mState.mIsMeasuring = true;
+                dispatchLayoutStep2();
+                // now we can get the width and height from the children.
+                mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
+            }
+        } else {
+            if (mHasFixedSize) {
+                mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
+                return;
+            }
+            // custom onMeasure
+            if (mAdapterUpdateDuringMeasure) {
+                eatRequestLayout();
+                processAdapterUpdatesAndSetAnimationFlags();
+
+                if (mState.mRunPredictiveAnimations) {
+                    mState.mInPreLayout = true;
+                } else {
+                    // consume remaining updates to provide a consistent state with the layout pass.
+                    mAdapterHelper.consumeUpdatesInOnePass();
+                    mState.mInPreLayout = false;
+                }
+                mAdapterUpdateDuringMeasure = false;
+                resumeRequestLayout(false);
+            }
+
+            if (mAdapter != null) {
+                mState.mItemCount = mAdapter.getItemCount();
+            } else {
+                mState.mItemCount = 0;
+            }
+            eatRequestLayout();
+            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
+            resumeRequestLayout(false);
+            mState.mInPreLayout = false; // clear
+        }
     }
 
     /**
@@ -2626,6 +2686,7 @@
         super.onSizeChanged(w, h, oldw, oldh);
         if (w != oldw || h != oldh) {
             invalidateGlows();
+            // layout's w/h are updated during measure/layout steps.
         }
     }
 
@@ -2824,14 +2885,46 @@
     void dispatchLayout() {
         if (mAdapter == null) {
             Log.e(TAG, "No adapter attached; skipping layout");
+            // leave the state in START
             return;
         }
         if (mLayout == null) {
             Log.e(TAG, "No layout manager attached; skipping layout");
+            // leave the state in START
             return;
         }
-        mViewInfoStore.clear();
+        mState.mIsMeasuring = false;
+        onEnterLayoutOrScroll();
+        if (mState.mLayoutStep == State.STEP_START) {
+            dispatchLayoutStep1();
+            mLayout.setExactMeasureSpecsFrom(this);
+            dispatchLayoutStep2();
+        } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth() ||
+                mLayout.getHeight() != getHeight()) {
+            // First 2 steps are done in onMeasure but looks like we have to run again due to
+            // changed size.
+            mLayout.setExactMeasureSpecsFrom(this);
+            dispatchLayoutStep2();
+        } else {
+            // always make sure we sync them (to ensure mode is exact)
+            mLayout.setExactMeasureSpecsFrom(this);
+        }
+        dispatchLayoutStep3();
+        onExitLayoutOrScroll();
+    }
+
+    /**
+     * The first step of a layout where we;
+     * - process adapter updates
+     * - decide which animation should run
+     * - save information about current views
+     * - If necessary, run predictive layout and save its information
+     */
+    private void dispatchLayoutStep1() {
+        mState.assertLayoutStep(State.STEP_START);
+        mState.mIsMeasuring = false;
         eatRequestLayout();
+        mViewInfoStore.clear();
         onEnterLayoutOrScroll();
 
         processAdapterUpdatesAndSetAnimationFlags();
@@ -2909,7 +3002,20 @@
         } else {
             clearOldPositions();
         }
-        mAdapterHelper.consumePostponedUpdates();
+        onExitLayoutOrScroll();
+        resumeRequestLayout(false);
+        mState.mLayoutStep = State.STEP_LAYOUT;
+    }
+
+    /**
+     * The second layout step where we do the actual layout of the views for the final state.
+     * This step might be run multiple times if necessary (e.g. measure).
+     */
+    private void dispatchLayoutStep2() {
+        eatRequestLayout();
+        onEnterLayoutOrScroll();
+        mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
+        mAdapterHelper.consumeUpdatesInOnePass();
         mState.mItemCount = mAdapter.getItemCount();
         mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
 
@@ -2922,7 +3028,19 @@
 
         // onLayoutChildren may have caused client code to disable item animations; re-check
         mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
+        mState.mLayoutStep = State.STEP_ANIMATIONS;
+        onExitLayoutOrScroll();
+        resumeRequestLayout(false);
+    }
 
+    /**
+     * The final step of the layout where we save the information about views for animations,
+     * trigger animations and do any necessary cleanup.
+     */
+    private void dispatchLayoutStep3() {
+        mState.assertLayoutStep(State.STEP_ANIMATIONS);
+        eatRequestLayout();
+        mState.mLayoutStep = State.STEP_START;
         if (mState.mRunSimpleAnimations) {
             // Step 3: Find out where things are now, and process change animations.
             int count = mChildHelper.getChildCount();
@@ -2952,18 +3070,18 @@
             // Step 4: Process view info lists and trigger animations
             mViewInfoStore.process(mViewInfoProcessCallback);
         }
-        resumeRequestLayout(false);
+
         mLayout.removeAndRecycleScrapInt(mRecycler);
         mState.mPreviousLayoutItemCount = mState.mItemCount;
         mDataSetHasChangedAfterLayout = false;
         mState.mRunSimpleAnimations = false;
 
         mState.mRunPredictiveAnimations = false;
-        onExitLayoutOrScroll();
         mLayout.mRequestedSimpleAnimations = false;
         if (mRecycler.mChangedScrap != null) {
             mRecycler.mChangedScrap.clear();
         }
+        resumeRequestLayout(false);
         mViewInfoStore.clear();
         if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) {
             dispatchOnScrolled(0, 0);
@@ -3133,17 +3251,15 @@
 
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
-        eatRequestLayout();
         TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
         dispatchLayout();
         TraceCompat.endSection();
-        resumeRequestLayout(false);
         mFirstLayoutComplete = true;
     }
 
     @Override
     public void requestLayout() {
-        if (!mEatRequestLayout && !mLayoutFrozen) {
+        if (mEatRequestLayout == 0 && !mLayoutFrozen) {
             super.requestLayout();
         } else {
             mLayoutRequestEaten = true;
@@ -3473,7 +3589,7 @@
     }
 
     /**
-     * Traverses the ascendants of the given view and returns the item view that contains it and
+     * Traverses the ancestors of the given view and returns the item view that contains it and
      * also a direct child of the RecyclerView. This returned view can be used to get the
      * ViewHolder by calling {@link #getChildViewHolder(View)}.
      *
@@ -5807,7 +5923,6 @@
                 mOnChildAttachStateListeners.get(i).onChildViewAttachedToWindow(child);
             }
         }
-
     }
 
     /**
@@ -5835,17 +5950,119 @@
 
         private boolean mRequestedSimpleAnimations = false;
 
-        private boolean mIsAttachedToWindow = false;
+        boolean mIsAttachedToWindow = false;
+
+        private boolean mAutoMeasure = false;
+
+        /**
+         * LayoutManager has its own more strict measurement cache to avoid re-measuring a child
+         * if the space that will be given to it is already larger than what it has measured before.
+         */
+        private boolean mMeasurementCacheEnabled = true;
+
+
+        /**
+         * These measure specs might be the measure specs that were passed into RecyclerView's
+         * onMeasure method OR fake measure specs created by the RecyclerView.
+         * For example, when a layout is run, RecyclerView always sets these specs to be
+         * EXACTLY because a LayoutManager cannot resize RecyclerView during a layout pass.
+         */
+        private int mWidthSpec, mHeightSpec;
 
         void setRecyclerView(RecyclerView recyclerView) {
             if (recyclerView == null) {
                 mRecyclerView = null;
                 mChildHelper = null;
+                mWidthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY);
+                mHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY);
             } else {
                 mRecyclerView = recyclerView;
                 mChildHelper = recyclerView.mChildHelper;
+                mWidthSpec = MeasureSpec
+                        .makeMeasureSpec(recyclerView.getWidth(), MeasureSpec.EXACTLY);
+                mHeightSpec = MeasureSpec
+                        .makeMeasureSpec(recyclerView.getHeight(), MeasureSpec.EXACTLY);
             }
+        }
 
+        void setMeasureSpecs(int wSpec, int hSpec) {
+            mWidthSpec = wSpec;
+            mHeightSpec = hSpec;
+        }
+
+        /**
+         * Called after a layout is calculated during a measure pass when using auto-measure.
+         * <p>
+         * It simply traverses all children to calculate a bounding box then calls
+         * {@link #setMeasuredDimension(Rect, int, int)}. LayoutManagers can override that method
+         * if they need to handle the bounding box differently.
+         * <p>
+         * For example, GridLayoutManager override that method to ensure that even if a column is
+         * empty, the GridLayoutManager still measures wide enough to include it.
+         *
+         * @param widthSpec The widthSpec that was passing into RecyclerView's onMeasure
+         * @param heightSpec The heightSpec that was passing into RecyclerView's onMeasure
+         */
+        void setMeasuredDimensionFromChildren(int widthSpec, int heightSpec) {
+            final int count = getChildCount();
+            if (count == 0) {
+                mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
+                return;
+            }
+            int minX = Integer.MAX_VALUE;
+            int minY = Integer.MAX_VALUE;
+            int maxX = Integer.MIN_VALUE;
+            int maxY = Integer.MIN_VALUE;
+
+            for (int i = 0; i < count; i++) {
+                View child = getChildAt(i);
+                LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                int left = getDecoratedLeft(child) - lp.leftMargin;
+                int right = getDecoratedRight(child) + lp.rightMargin;
+                int top = getDecoratedTop(child) - lp.topMargin;
+                int bottom = getDecoratedBottom(child) + lp.bottomMargin;
+                if (left < minX) {
+                    minX = left;
+                }
+                if (right > maxX) {
+                    maxX = right;
+                }
+                if (top < minY) {
+                    minY = top;
+                }
+                if (bottom > maxY) {
+                    maxY = bottom;
+                }
+            }
+            mRecyclerView.mTempRect.set(minX, minY, maxX, maxY);
+            setMeasuredDimension(mRecyclerView.mTempRect, widthSpec, heightSpec);
+        }
+
+        /**
+         * Sets the measured dimensions from the given bounding box of the children and the
+         * measurement specs that were passed into {@link RecyclerView#onMeasure(int, int)}. It is
+         * called after the RecyclerView calls
+         * {@link LayoutManager#onLayoutChildren(Recycler, State)} during a measurement pass.
+         * <p>
+         * This method should call {@link #setMeasuredDimension(int, int)}.
+         * <p>
+         * The default implementation adds the RecyclerView's padding to the given bounding box
+         * then caps the value to be within the given measurement specs.
+         * <p>
+         * This method is only called if the LayoutManager opted into the auto measurement API.
+         *
+         * @param childrenBounds The bounding box of all children
+         * @param wSpec The widthMeasureSpec that was passed into the RecyclerView.
+         * @param hSpec The heightMeasureSpec that was passed into the RecyclerView.
+         *
+         * @see #setAutoMeasureEnabled(boolean)
+         */
+        public void setMeasuredDimension(Rect childrenBounds, int wSpec, int hSpec) {
+            int usedWidth = childrenBounds.width() + getPaddingLeft() + getPaddingRight();
+            int usedHeight = childrenBounds.height() + getPaddingTop() + getPaddingBottom();
+            int width = chooseSize(wSpec, usedWidth, getMinimumWidth());
+            int height = chooseSize(hSpec, usedHeight, getMinimumHeight());
+            setMeasuredDimension(width, height);
         }
 
         /**
@@ -5871,6 +6088,31 @@
         }
 
         /**
+         * Chooses a size from the given specs and parameters that is closest to the desired size
+         * and also complies with the spec.
+         *
+         * @param spec The measureSpec
+         * @param desired The preferred measurement
+         * @param min The minimum value
+         *
+         * @return A size that fits to the given specs
+         */
+        public static int chooseSize(int spec, int desired, int min) {
+            final int mode = View.MeasureSpec.getMode(spec);
+            final int size = View.MeasureSpec.getSize(spec);
+            switch (mode) {
+                case View.MeasureSpec.EXACTLY:
+                    return size;
+                case View.MeasureSpec.AT_MOST:
+                    desired = Math.min(size, desired);
+                    // flow through
+                case View.MeasureSpec.UNSPECIFIED:
+                default:
+                    return Math.max(desired, min);
+            }
+        }
+
+        /**
          * Checks if RecyclerView is in the middle of a layout or scroll and throws an
          * {@link IllegalStateException} if it <b>is</b>.
          *
@@ -5884,6 +6126,86 @@
         }
 
         /**
+         * Defines whether the layout should be measured by the RecyclerView or the LayoutManager
+         * wants to handle the layout measurements itself.
+         * <p>
+         * This method is usually called by the LayoutManager with value {@code true} if it wants
+         * to support WRAP_CONTENT. If you are using a public LayoutManager but want to customize
+         * the measurement logic, you can call this method with {@code false} and override
+         * {@link LayoutManager#onMeasure(int, int)} to implement your custom measurement logic.
+         * <p>
+         * AutoMeasure is a convenience mechanism for LayoutManagers to easily wrap their content or
+         * handle various specs provided by the RecyclerView's parent.
+         * It works by calling {@link LayoutManager#onLayoutChildren(Recycler, State)} during an
+         * {@link RecyclerView#onMeasure(int, int)} call, then calculating desired dimensions based
+         * on children's positions. It does this while supporting all existing animation
+         * capabilities of the RecyclerView.
+         * <p>
+         * AutoMeasure works as follows:
+         * <ol>
+         * <li>LayoutManager should call {@code setAutoMeasureEnabled(true)} to enable it. All of
+         * the framework LayoutManagers use {@code auto-measure}.</li>
+         * <li>When {@link RecyclerView#onMeasure(int, int)} is called, if the provided specs are
+         * exact, RecyclerView will only call LayoutManager's {@code onMeasure} and return without
+         * doing any layout calculation.</li>
+         * <li>If one of the layout specs is not {@code EXACT}, the RecyclerView will start the
+         * layout process in {@code onMeasure} call. It will process all pending Adapter updates and
+         * decide whether to run a predictive layout or not. If it decides to do so, it will first
+         * call {@link #onLayoutChildren(Recycler, State)} with {@link State#isPreLayout()} set to
+         * {@code true}. At this stage, {@link #getWidth()} and {@link #getHeight()} will still
+         * return the width and height of the RecyclerView as of the last layout calculation.
+         * <p>
+         * After handling the predictive case, RecyclerView will call
+         * {@link #onLayoutChildren(Recycler, State)} with {@link State#isMeasuring()} set to
+         * {@code true} and {@link State#isPreLayout()} set to {@code false}. The LayoutManager can
+         * access the measurement specs via {@link #getHeight()}, {@link #getHeightMode()},
+         * {@link #getWidth()} and {@link #getWidthMode()}.</li>
+         * <li>After the layout calculation, RecyclerView sets the measured width & height by
+         * calculating the bounding box for the children (+ RecyclerView's padding). The
+         * LayoutManagers can override {@link #setMeasuredDimension(Rect, int, int)} to choose
+         * different values. For instance, GridLayoutManager overrides this value to handle the case
+         * where if it is vertical and has 3 columns but only 2 items, it should still measure its
+         * width to fit 3 items, not 2.</li>
+         * <li>Any following on measure call to the RecyclerView will run
+         * {@link #onLayoutChildren(Recycler, State)} with {@link State#isMeasuring()} set to
+         * {@code true} and {@link State#isPreLayout()} set to {@code false}. RecyclerView will
+         * take care of which views are actually added / removed / moved / changed for animations so
+         * that the LayoutManager should not worry about them and handle each
+         * {@link #onLayoutChildren(Recycler, State)} call as if it is the last one.
+         * </li>
+         * <li>When measure is complete and RecyclerView's
+         * {@link #onLayout(boolean, int, int, int, int)} method is called, RecyclerView checks
+         * whether it already did layout calculations during the measure pass and if so, it re-uses
+         * that information. It may still decide to call {@link #onLayoutChildren(Recycler, State)}
+         * if the last measure spec was different from the final dimensions or adapter contents
+         * have changed between the measure call and the layout call.</li>
+         * <li>Finally, animations are calculated and run as usual.</li>
+         * </ol>
+         *
+         * @param enabled <code>True</code> if the Layout should be measured by the
+         *                             RecyclerView, <code>false</code> if the LayoutManager wants
+         *                             to measure itself.
+         *
+         * @see #setMeasuredDimension(Rect, int, int)
+         * @see #isAutoMeasureEnabled()
+         */
+        public void setAutoMeasureEnabled(boolean enabled) {
+            mAutoMeasure = enabled;
+        }
+
+        /**
+         * Returns whether the LayoutManager uses the automatic measurement API or not.
+         *
+         * @return <code>True</code> if the LayoutManager is measured by the RecyclerView or
+         * <code>false</code> if it measures itself.
+         *
+         * @see #setAutoMeasureEnabled(boolean)
+         */
+        public boolean isAutoMeasureEnabled() {
+            return mAutoMeasure;
+        }
+
+        /**
          * Returns whether this LayoutManager supports automatic item animations.
          * A LayoutManager wishing to support item animations should obey certain
          * rules as outlined in {@link #onLayoutChildren(Recycler, State)}.
@@ -6447,7 +6769,7 @@
         }
 
         /**
-         * Traverses the ascendants of the given view and returns the item view that contains it
+         * Traverses the ancestors of the given view and returns the item view that contains it
          * and also a direct child of the LayoutManager.
          * <p>
          * Note that this method may return null if the view is a child of the RecyclerView but
@@ -6695,12 +7017,48 @@
         }
 
         /**
+         * Return the width measurement spec mode of the RecyclerView.
+         * <p>
+         * This value is set only if the LayoutManager opts into the auto measure api via
+         * {@link #setAutoMeasureEnabled(boolean)}.
+         * <p>
+         * When RecyclerView is running a layout, this value is always set to
+         * {@link MeasureSpec#EXACTLY} even if it was measured with a different spec mode.
+         *
+         * @return Width measure spec mode.
+         *
+         * @see MeasureSpec#getMode(int)
+         * @see View#onMeasure(int, int)
+         */
+        public int getWidthMode() {
+            return MeasureSpec.getMode(mWidthSpec);
+        }
+
+        /**
+         * Return the height measurement spec mode of the RecyclerView.
+         * <p>
+         * This value is set only if the LayoutManager opts into the auto measure api via
+         * {@link #setAutoMeasureEnabled(boolean)}.
+         * <p>
+         * When RecyclerView is running a layout, this value is always set to
+         * {@link MeasureSpec#EXACTLY} even if it was measured with a different spec mode.
+         *
+         * @return Height measure spec mode.
+         *
+         * @see MeasureSpec#getMode(int)
+         * @see View#onMeasure(int, int)
+         */
+        public int getHeightMode() {
+            return MeasureSpec.getMode(mHeightSpec);
+        }
+
+        /**
          * Return the width of the parent RecyclerView
          *
          * @return Width in pixels
          */
         public int getWidth() {
-            return mRecyclerView != null ? mRecyclerView.getWidth() : 0;
+            return MeasureSpec.getSize(mWidthSpec);
         }
 
         /**
@@ -6709,7 +7067,7 @@
          * @return Height in pixels
          */
         public int getHeight() {
-            return mRecyclerView != null ? mRecyclerView.getHeight() : 0;
+            return MeasureSpec.getSize(mHeightSpec);
         }
 
         /**
@@ -6914,6 +7272,7 @@
             } else {
                 detachViewAt(index);
                 recycler.scrapView(view);
+                mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
             }
         }
 
@@ -6974,14 +7333,85 @@
             final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
             widthUsed += insets.left + insets.right;
             heightUsed += insets.top + insets.bottom;
-
-            final int widthSpec = getChildMeasureSpec(getWidth(),
+            final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(),
                     getPaddingLeft() + getPaddingRight() + widthUsed, lp.width,
                     canScrollHorizontally());
-            final int heightSpec = getChildMeasureSpec(getHeight(),
+            final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
                     getPaddingTop() + getPaddingBottom() + heightUsed, lp.height,
                     canScrollVertically());
-            child.measure(widthSpec, heightSpec);
+            if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
+                child.measure(widthSpec, heightSpec);
+            }
+        }
+
+        /**
+         * RecyclerView internally does its own View measurement caching which should help with
+         * WRAP_CONTENT.
+         * <p>
+         * Use this method if the View is already measured once in this layout pass.
+         */
+        boolean shouldReMeasureChild(View child, int widthSpec, int heightSpec, LayoutParams lp) {
+            return !mMeasurementCacheEnabled
+                    || !isMeasurementUpToDate(child.getMeasuredWidth(), widthSpec, lp.width)
+                    || !isMeasurementUpToDate(child.getMeasuredHeight(), heightSpec, lp.height);
+        }
+
+        // we may consider making this public
+        /**
+         * RecyclerView internally does its own View measurement caching which should help with
+         * WRAP_CONTENT.
+         * <p>
+         * Use this method if the View is not yet measured and you need to decide whether to
+         * measure this View or not.
+         */
+        boolean shouldMeasureChild(View child, int widthSpec, int heightSpec, LayoutParams lp) {
+            return child.isLayoutRequested()
+                    || !mMeasurementCacheEnabled
+                    || !isMeasurementUpToDate(child.getWidth(), widthSpec, lp.width)
+                    || !isMeasurementUpToDate(child.getHeight(), heightSpec, lp.height);
+        }
+
+        /**
+         * In addition to the View Framework's measurement cache, RecyclerView uses its own
+         * additional measurement cache for its children to avoid re-measuring them when not
+         * necessary. It is on by default but it can be turned off via
+         * {@link #setMeasurementCacheEnabled(boolean)}.
+         *
+         * @return True if measurement cache is enabled, false otherwise.
+         *
+         * @see #setMeasurementCacheEnabled(boolean)
+         */
+        public boolean isMeasurementCacheEnabled() {
+            return mMeasurementCacheEnabled;
+        }
+
+        /**
+         * Sets whether RecyclerView should use its own measurement cache for the children. This is
+         * a more aggressive cache than the framework uses.
+         *
+         * @param measurementCacheEnabled True to enable the measurement cache, false otherwise.
+         *
+         * @see #isMeasurementCacheEnabled()
+         */
+        public void setMeasurementCacheEnabled(boolean measurementCacheEnabled) {
+            mMeasurementCacheEnabled = measurementCacheEnabled;
+        }
+
+        private static boolean isMeasurementUpToDate(int childSize, int spec, int dimension) {
+            final int specMode = MeasureSpec.getMode(spec);
+            final int specSize = MeasureSpec.getSize(spec);
+            if (dimension > 0 && childSize != dimension) {
+                return false;
+            }
+            switch (specMode) {
+                case MeasureSpec.UNSPECIFIED:
+                    return true;
+                case MeasureSpec.AT_MOST:
+                    return specSize >= childSize;
+                case MeasureSpec.EXACTLY:
+                    return  specSize == childSize;
+            }
+            return false;
         }
 
         /**
@@ -7003,40 +7433,43 @@
             widthUsed += insets.left + insets.right;
             heightUsed += insets.top + insets.bottom;
 
-            final int widthSpec = getChildMeasureSpec(getWidth(),
+            final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(),
                     getPaddingLeft() + getPaddingRight() +
                             lp.leftMargin + lp.rightMargin + widthUsed, lp.width,
                     canScrollHorizontally());
-            final int heightSpec = getChildMeasureSpec(getHeight(),
+            final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
                     getPaddingTop() + getPaddingBottom() +
                             lp.topMargin + lp.bottomMargin + heightUsed, lp.height,
                     canScrollVertically());
-            child.measure(widthSpec, heightSpec);
+            if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
+                child.measure(widthSpec, heightSpec);
+            }
         }
 
         /**
          * Calculate a MeasureSpec value for measuring a child view in one dimension.
          *
          * @param parentSize Size of the parent view where the child will be placed
-         * @param padding Total space currently consumed by other elements of parent
-         * @param childDimension Desired size of the child view, or MATCH_PARENT/WRAP_CONTENT.
+         * @param padding Total space currently consumed by other elements of the parent
+         * @param childDimension Desired size of the child view, or FILL_PARENT/WRAP_CONTENT.
          *                       Generally obtained from the child view's LayoutParams
          * @param canScroll true if the parent RecyclerView can scroll in this dimension
          *
          * @return a MeasureSpec value for the child view
+         * @deprecated use {@link #getChildMeasureSpec(int, int, int, int, boolean)}
          */
+        @Deprecated
         public static int getChildMeasureSpec(int parentSize, int padding, int childDimension,
                 boolean canScroll) {
             int size = Math.max(0, parentSize - padding);
             int resultSize = 0;
             int resultMode = 0;
-
             if (canScroll) {
                 if (childDimension >= 0) {
                     resultSize = childDimension;
                     resultMode = MeasureSpec.EXACTLY;
                 } else {
-                    // MATCH_PARENT can't be applied since we can scroll in this dimension, wrap
+                    // FILL_PARENT can't be applied since we can scroll in this dimension, wrap
                     // instead using UNSPECIFIED.
                     resultSize = 0;
                     resultMode = MeasureSpec.UNSPECIFIED;
@@ -7047,6 +7480,7 @@
                     resultMode = MeasureSpec.EXACTLY;
                 } else if (childDimension == LayoutParams.FILL_PARENT) {
                     resultSize = size;
+                    // TODO this should be my spec.
                     resultMode = MeasureSpec.EXACTLY;
                 } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                     resultSize = size;
@@ -7057,6 +7491,63 @@
         }
 
         /**
+         * Calculate a MeasureSpec value for measuring a child view in one dimension.
+         *
+         * @param parentSize Size of the parent view where the child will be placed
+         * @param parentMode The measurement spec mode of the parent
+         * @param padding Total space currently consumed by other elements of parent
+         * @param childDimension Desired size of the child view, or FILL_PARENT/WRAP_CONTENT.
+         *                       Generally obtained from the child view's LayoutParams
+         * @param canScroll true if the parent RecyclerView can scroll in this dimension
+         *
+         * @return a MeasureSpec value for the child view
+         */
+        public static int getChildMeasureSpec(int parentSize, int parentMode, int padding,
+                int childDimension, boolean canScroll) {
+            int size = Math.max(0, parentSize - padding);
+            int resultSize = 0;
+            int resultMode = 0;
+            if (canScroll) {
+                if (childDimension >= 0) {
+                    resultSize = childDimension;
+                    resultMode = MeasureSpec.EXACTLY;
+                } else if (childDimension == LayoutParams.FILL_PARENT){
+                    switch (parentMode) {
+                        case MeasureSpec.AT_MOST:
+                        case MeasureSpec.EXACTLY:
+                            resultSize = size;
+                            resultMode = parentMode;
+                            break;
+                        case MeasureSpec.UNSPECIFIED:
+                            resultSize = 0;
+                            resultMode = MeasureSpec.UNSPECIFIED;
+                            break;
+                    }
+                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
+                    resultSize = 0;
+                    resultMode = MeasureSpec.UNSPECIFIED;
+                }
+            } else {
+                if (childDimension >= 0) {
+                    resultSize = childDimension;
+                    resultMode = MeasureSpec.EXACTLY;
+                } else if (childDimension == LayoutParams.FILL_PARENT) {
+                    resultSize = size;
+                    resultMode = parentMode;
+                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
+                    resultSize = size;
+                    if (parentMode == MeasureSpec.AT_MOST || parentMode == MeasureSpec.EXACTLY) {
+                        resultMode = MeasureSpec.AT_MOST;
+                    } else {
+                        resultMode = MeasureSpec.UNSPECIFIED;
+                    }
+
+                }
+            }
+            return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
+        }
+
+        /**
          * Returns the measured width of the given child, plus the additional size of
          * any insets applied by {@link ItemDecoration ItemDecorations}.
          *
@@ -7993,6 +8484,39 @@
             return properties;
         }
 
+        void setExactMeasureSpecsFrom(RecyclerView recyclerView) {
+            setMeasureSpecs(
+                    MeasureSpec.makeMeasureSpec(recyclerView.getWidth(), MeasureSpec.EXACTLY),
+                    MeasureSpec.makeMeasureSpec(recyclerView.getHeight(), MeasureSpec.EXACTLY)
+            );
+        }
+
+        /**
+         * Internal API to allow LayoutManagers to be measured twice.
+         * <p>
+         * This is not public because LayoutManagers should be able to handle their layouts in one
+         * pass but it is very convenient to make existing LayoutManagers support wrapping content
+         * when both orientations are undefined.
+         * <p>
+         * This API will be removed after default LayoutManagers properly implement wrap content in
+         * non-scroll orientation.
+         */
+        boolean shouldMeasureTwice() {
+            return false;
+        }
+
+        boolean hasFlexibleChildInBothOrientations() {
+            final int childCount = getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                final View child = getChildAt(i);
+                final ViewGroup.LayoutParams lp = child.getLayoutParams();
+                if (lp.width < 0 && lp.height < 0) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
         /**
          * Some general properties that a LayoutManager may want to use.
          */
@@ -9481,9 +10005,29 @@
      * data between your components without needing to manage their lifecycles.</p>
      */
     public static class State {
+        static final int STEP_START = 1;
+        static final int STEP_LAYOUT = 1 << 1;
+        static final int STEP_ANIMATIONS = 1 << 2;
+
+        void assertLayoutStep(int accepted) {
+            if ((accepted & mLayoutStep) == 0) {
+                throw new IllegalStateException("Layout state should be one of "
+                        + Integer.toBinaryString(accepted) + " but it is "
+                        + Integer.toBinaryString(mLayoutStep));
+            }
+        }
+
+        @IntDef(flag = true, value = {
+                STEP_START, STEP_LAYOUT, STEP_ANIMATIONS
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        @interface LayoutState {}
 
         private int mTargetPosition = RecyclerView.NO_POSITION;
 
+        @LayoutState
+        private int mLayoutStep = STEP_START;
+
         private SparseArray<Object> mData;
 
         /**
@@ -9512,6 +10056,8 @@
 
         private boolean mTrackOldChangeHolders = false;
 
+        private boolean mIsMeasuring = false;
+
         State reset() {
             mTargetPosition = RecyclerView.NO_POSITION;
             if (mData != null) {
@@ -9519,9 +10065,32 @@
             }
             mItemCount = 0;
             mStructureChanged = false;
+            mIsMeasuring = false;
             return this;
         }
 
+        /**
+         * Returns true if the RecyclerView is currently measuring the layout. This value is
+         * {@code true} only if the LayoutManager opted into the auto measure API and RecyclerView
+         * has non-exact measurement specs.
+         * <p>
+         * Note that if the LayoutManager supports predictive animations and it is calculating the
+         * pre-layout step, this value will be {@code false} even if the RecyclerView is in
+         * {@code onMeasure} call. This is because pre-layout means the previous state of the
+         * RecyclerView and measurements made for that state cannot change the RecyclerView's size.
+         * LayoutManager is always guaranteed to receive another call to
+         * {@link LayoutManager#onLayoutChildren(Recycler, State)} when this happens.
+         *
+         * @return True if the RecyclerView is currently calculating its bounds, false otherwise.
+         */
+        public boolean isMeasuring() {
+            return mIsMeasuring;
+        }
+
+        /**
+         * Returns true if
+         * @return
+         */
         public boolean isPreLayout() {
             return mInPreLayout;
         }
@@ -9890,7 +10459,7 @@
          *
          * @see #recordPostLayoutInformation(State, ViewHolder)
          * @see #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
-         * @see #animateDisappearance(ViewHolder, ItemHolderInfo)
+         * @see #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
          * @see #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)
          * @see #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)
          */
diff --git a/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
index 3147d24..a121ec9 100644
--- a/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
@@ -30,7 +30,6 @@
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewParent;
 import android.view.accessibility.AccessibilityEvent;
 
 import java.util.ArrayList;
@@ -38,10 +37,10 @@
 import java.util.BitSet;
 import java.util.List;
 
-import static android.support.v7.widget.LayoutState.LAYOUT_START;
-import static android.support.v7.widget.LayoutState.LAYOUT_END;
 import static android.support.v7.widget.LayoutState.ITEM_DIRECTION_HEAD;
 import static android.support.v7.widget.LayoutState.ITEM_DIRECTION_TAIL;
+import static android.support.v7.widget.LayoutState.LAYOUT_END;
+import static android.support.v7.widget.LayoutState.LAYOUT_START;
 import static android.support.v7.widget.RecyclerView.NO_POSITION;
 
 /**
@@ -177,7 +176,7 @@
     /**
      * Re-used measurement specs. updated by onLayout.
      */
-    private int mFullSizeSpec, mWidthSpec, mHeightSpec;
+    private int mFullSizeSpec;
 
     /**
      * Re-used rectangle to get child decor offsets.
@@ -221,6 +220,7 @@
         setOrientation(properties.orientation);
         setSpanCount(properties.spanCount);
         setReverseLayout(properties.reverseLayout);
+        setAutoMeasureEnabled(mGapStrategy != GAP_HANDLING_NONE);
     }
 
     /**
@@ -233,6 +233,7 @@
     public StaggeredGridLayoutManager(int spanCount, int orientation) {
         mOrientation = orientation;
         setSpanCount(spanCount);
+        setAutoMeasureEnabled(mGapStrategy != GAP_HANDLING_NONE);
     }
 
     /**
@@ -373,10 +374,16 @@
     private boolean checkSpanForGap(Span span) {
         if (mShouldReverseLayout) {
             if (span.getEndLine() < mPrimaryOrientation.getEndAfterPadding()) {
-                return true;
+                // if it is full span, it is OK
+                final View endView = span.mViews.get(span.mViews.size() - 1);
+                final LayoutParams lp = span.getLayoutParams(endView);
+                return !lp.mFullSpan;
             }
         } else if (span.getStartLine() > mPrimaryOrientation.getStartAfterPadding()) {
-            return true;
+            // if it is full span, it is OK
+            final View startView = span.mViews.get(0);
+            final LayoutParams lp = span.getLayoutParams(startView);
+            return !lp.mFullSpan;
         }
         return false;
     }
@@ -488,6 +495,7 @@
                     + "or GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS");
         }
         mGapStrategy = gapStrategy;
+        setAutoMeasureEnabled(mGapStrategy != GAP_HANDLING_NONE);
         requestLayout();
     }
 
@@ -556,8 +564,31 @@
     public boolean getReverseLayout() {
         return mReverseLayout;
     }
+
+    @Override
+    public void setMeasuredDimension(Rect childrenBounds, int wSpec, int hSpec) {
+        // we don't like it to wrap content in our non-scroll direction.
+        final int width, height;
+        if (mOrientation == VERTICAL) {
+            int usedHeight = childrenBounds.height() + getPaddingTop() + getPaddingBottom();
+            height = chooseSize(hSpec, usedHeight, getMinimumHeight());
+            width = chooseSize(wSpec, mSizePerSpan * mSpanCount, getMinimumWidth());
+        } else {
+            int usedWidth = childrenBounds.width() + getPaddingLeft() + getPaddingRight();
+            width = chooseSize(wSpec, usedWidth, getMinimumWidth());
+            height = chooseSize(hSpec, mSizePerSpan * mSpanCount, getMinimumHeight());
+        }
+        setMeasuredDimension(width, height);
+    }
+
     @Override
     public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+        onLayoutChildren(recycler, state, true);
+    }
+
+
+    private void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state,
+            boolean shouldCheckForGaps) {
         ensureOrientationHelper();
         final AnchorInfo anchorInfo = mAnchorInfo;
         anchorInfo.reset();
@@ -603,8 +634,9 @@
             }
         }
         detachAndScrapAttachedViews(recycler);
+        mLayoutState.mRecycle = false;
         mLaidOutInvalidFullSpan = false;
-        updateMeasureSpecs();
+        updateMeasureSpecs(mSecondaryOrientation.getTotalSpace());
         updateLayoutState(anchorInfo.mPosition, state);
         if (anchorInfo.mLayoutFromEnd) {
             // Layout start.
@@ -624,6 +656,8 @@
             fill(recycler, mLayoutState, state);
         }
 
+        repositionToWrapContentIfNecessary();
+
         if (getChildCount() > 0) {
             if (mShouldReverseLayout) {
                 fixEndGap(recycler, state, true);
@@ -633,14 +667,16 @@
                 fixEndGap(recycler, state, false);
             }
         }
-
-        if (!state.isPreLayout()) {
+        boolean hasGaps = false;
+        if (shouldCheckForGaps && !state.isPreLayout()) {
             final boolean needToCheckForGaps = mGapStrategy != GAP_HANDLING_NONE
                     && getChildCount() > 0
                     && (mLaidOutInvalidFullSpan || hasGapsToFix() != null);
             if (needToCheckForGaps) {
                 removeCallbacks(mCheckForGapsRunnable);
-                postOnAnimation(mCheckForGapsRunnable);
+                if (checkForGaps()) {
+                    hasGaps = true;
+                }
             }
             mPendingScrollPosition = NO_POSITION;
             mPendingScrollPositionOffset = INVALID_OFFSET;
@@ -648,6 +684,58 @@
         mLastLayoutFromEnd = anchorInfo.mLayoutFromEnd;
         mLastLayoutRTL = isLayoutRTL();
         mPendingSavedState = null; // we don't need this anymore
+        if (hasGaps) {
+            onLayoutChildren(recycler, state, false);
+        }
+    }
+
+    private void repositionToWrapContentIfNecessary() {
+        if (mSecondaryOrientation.getMode() == View.MeasureSpec.EXACTLY) {
+            return; // nothing to do
+        }
+        float maxSize = 0;
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i ++) {
+            View child = getChildAt(i);
+            float size = mSecondaryOrientation.getDecoratedMeasurement(child);
+            if (size < maxSize) {
+                continue;
+            }
+            LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
+            if (layoutParams.isFullSpan()) {
+                size = 1f * size / mSpanCount;
+            }
+            maxSize = Math.max(maxSize, size);
+        }
+        int before = mSizePerSpan;
+        int desired = Math.round(maxSize * mSpanCount);
+        if (mSecondaryOrientation.getMode() == View.MeasureSpec.AT_MOST) {
+            desired = Math.min(desired, mSecondaryOrientation.getTotalSpace());
+        }
+        updateMeasureSpecs(desired);
+        if (mSizePerSpan == before) {
+            return; // nothing has changed
+        }
+        for (int i = 0; i < childCount; i ++) {
+            View child = getChildAt(i);
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            if (lp.mFullSpan) {
+                continue;
+            }
+            if (isLayoutRTL() && mOrientation == VERTICAL) {
+                int newOffset = -(mSpanCount - 1 - lp.mSpan.mIndex) * mSizePerSpan;
+                int prevOffset = -(mSpanCount - 1 - lp.mSpan.mIndex) * before;
+                child.offsetLeftAndRight(newOffset - prevOffset);
+            } else {
+                int newOffset = lp.mSpan.mIndex * mSizePerSpan;
+                int prevOffset = lp.mSpan.mIndex * before;
+                if (mOrientation == VERTICAL) {
+                    child.offsetLeftAndRight(newOffset - prevOffset);
+                } else {
+                    child.offsetTopAndBottom(newOffset - prevOffset);
+                }
+            }
+        }
     }
 
     private void applyPendingSavedState(AnchorInfo anchorInfo) {
@@ -795,17 +883,11 @@
         return true;
     }
 
-    void updateMeasureSpecs() {
-        mSizePerSpan = mSecondaryOrientation.getTotalSpace() / mSpanCount;
+    void updateMeasureSpecs(int totalSpace) {
+        mSizePerSpan = totalSpace / mSpanCount;
+        //noinspection ResourceType
         mFullSizeSpec = View.MeasureSpec.makeMeasureSpec(
-                mSecondaryOrientation.getTotalSpace(), View.MeasureSpec.EXACTLY);
-        if (mOrientation == VERTICAL) {
-            mWidthSpec = View.MeasureSpec.makeMeasureSpec(mSizePerSpan, View.MeasureSpec.EXACTLY);
-            mHeightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
-        } else {
-            mHeightSpec = View.MeasureSpec.makeMeasureSpec(mSizePerSpan, View.MeasureSpec.EXACTLY);
-            mWidthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
-        }
+                totalSpace, mSecondaryOrientation.getMode());
     }
 
     @Override
@@ -1004,43 +1086,48 @@
         return computeScrollRange(state);
     }
 
-    private void measureChildWithDecorationsAndMargin(View child, LayoutParams lp) {
+    private void measureChildWithDecorationsAndMargin(View child, LayoutParams lp,
+            boolean alreadyMeasured) {
         if (lp.mFullSpan) {
             if (mOrientation == VERTICAL) {
                 measureChildWithDecorationsAndMargin(child, mFullSizeSpec,
-                        getSpecForDimension(lp.height, mHeightSpec));
+                        getChildMeasureSpec(getHeight(), getHeightMode(), 0, lp.height, true),
+                        alreadyMeasured);
             } else {
                 measureChildWithDecorationsAndMargin(child,
-                        getSpecForDimension(lp.width, mWidthSpec), mFullSizeSpec);
+                        getChildMeasureSpec(getWidth(), getWidthMode(), 0, lp.width, true),
+                        mFullSizeSpec, alreadyMeasured);
             }
         } else {
             if (mOrientation == VERTICAL) {
-                measureChildWithDecorationsAndMargin(child, mWidthSpec,
-                        getSpecForDimension(lp.height, mHeightSpec));
+                measureChildWithDecorationsAndMargin(child,
+                        getChildMeasureSpec(mSizePerSpan, getWidthMode(), 0, lp.width, false),
+                        getChildMeasureSpec(getHeight(), getHeightMode(), 0, lp.height, true),
+                        alreadyMeasured);
             } else {
                 measureChildWithDecorationsAndMargin(child,
-                        getSpecForDimension(lp.width, mWidthSpec), mHeightSpec);
+                        getChildMeasureSpec(getWidth(), getWidthMode(), 0, lp.width, true),
+                        getChildMeasureSpec(mSizePerSpan, getHeightMode(), 0, lp.height, false),
+                        alreadyMeasured);
             }
         }
     }
 
-    private int getSpecForDimension(int dim, int defaultSpec) {
-        if (dim < 0) {
-            return defaultSpec;
-        } else {
-            return View.MeasureSpec.makeMeasureSpec(dim, View.MeasureSpec.EXACTLY);
-        }
-    }
-
     private void measureChildWithDecorationsAndMargin(View child, int widthSpec,
-            int heightSpec) {
+            int heightSpec, boolean alreadyMeasured) {
         calculateItemDecorationsForChild(child, mTmpRect);
         LayoutParams lp = (LayoutParams) child.getLayoutParams();
         widthSpec = updateSpecWithExtra(widthSpec, lp.leftMargin + mTmpRect.left,
                 lp.rightMargin + mTmpRect.right);
         heightSpec = updateSpecWithExtra(heightSpec, lp.topMargin + mTmpRect.top,
                 lp.bottomMargin + mTmpRect.bottom);
-        child.measure(widthSpec, heightSpec);
+        final boolean measure = alreadyMeasured
+                ? shouldReMeasureChild(child, widthSpec, heightSpec, lp)
+                : shouldMeasureChild(child, widthSpec, heightSpec, lp);
+        if (measure) {
+            child.measure(widthSpec, heightSpec);
+        }
+
     }
 
     private int updateSpecWithExtra(int spec, int startInset, int endInset) {
@@ -1253,7 +1340,10 @@
 
     private void fixEndGap(RecyclerView.Recycler recycler, RecyclerView.State state,
             boolean canOffsetChildren) {
-        final int maxEndLine = getMaxEnd(mPrimaryOrientation.getEndAfterPadding());
+        final int maxEndLine = getMaxEnd(Integer.MIN_VALUE);
+        if (maxEndLine == Integer.MIN_VALUE) {
+            return;
+        }
         int gap = mPrimaryOrientation.getEndAfterPadding() - maxEndLine;
         int fixOffset;
         if (gap > 0) {
@@ -1269,7 +1359,10 @@
 
     private void fixStartGap(RecyclerView.Recycler recycler, RecyclerView.State state,
             boolean canOffsetChildren) {
-        final int minStartLine = getMinStart(mPrimaryOrientation.getStartAfterPadding());
+        final int minStartLine = getMinStart(Integer.MAX_VALUE);
+        if (minStartLine == Integer.MAX_VALUE) {
+            return;
+        }
         int gap = minStartLine - mPrimaryOrientation.getStartAfterPadding();
         int fixOffset;
         if (gap > 0) {
@@ -1309,6 +1402,8 @@
             mLayoutState.mStartLine = -startExtra;
         }
         mLayoutState.mStopInFocusable = false;
+        mLayoutState.mRecycle = true;
+        mLayoutState.mInfinite = mPrimaryOrientation.getMode() == View.MeasureSpec.UNSPECIFIED;
     }
 
     private void setLayoutStateDirection(int direction) {
@@ -1413,10 +1508,18 @@
         final int targetLine;
 
         // Line of the furthest row.
-        if (layoutState.mLayoutDirection == LAYOUT_END) {
-            targetLine = layoutState.mEndLine + layoutState.mAvailable;
-        } else { // LAYOUT_START
-            targetLine = layoutState.mStartLine - layoutState.mAvailable;
+        if (mLayoutState.mInfinite) {
+            if (layoutState.mLayoutDirection == LAYOUT_END) {
+                targetLine = Integer.MAX_VALUE;
+            } else { // LAYOUT_START
+                targetLine = Integer.MIN_VALUE;
+            }
+        } else {
+            if (layoutState.mLayoutDirection == LAYOUT_END) {
+                targetLine = layoutState.mEndLine + layoutState.mAvailable;
+            } else { // LAYOUT_START
+                targetLine = layoutState.mStartLine - layoutState.mAvailable;
+            }
         }
 
         updateAllRemainingSpans(layoutState.mLayoutDirection, targetLine);
@@ -1430,7 +1533,8 @@
                 ? mPrimaryOrientation.getEndAfterPadding()
                 : mPrimaryOrientation.getStartAfterPadding();
         boolean added = false;
-        while (layoutState.hasMore(state) && !mRemainingSpans.isEmpty()) {
+        while (layoutState.hasMore(state)
+                && (mLayoutState.mInfinite || !mRemainingSpans.isEmpty())) {
             View view = layoutState.next(recycler);
             LayoutParams lp = ((LayoutParams) view.getLayoutParams());
             final int position = lp.getViewLayoutPosition();
@@ -1456,7 +1560,7 @@
             } else {
                 addView(view, 0);
             }
-            measureChildWithDecorationsAndMargin(view, lp);
+            measureChildWithDecorationsAndMargin(view, lp, false);
 
             final int start;
             final int end;
@@ -1506,10 +1610,20 @@
                 }
             }
             attachViewToSpans(view, lp, layoutState);
-            final int otherStart = lp.mFullSpan ? mSecondaryOrientation.getStartAfterPadding()
-                    : currentSpan.mIndex * mSizePerSpan +
-                            mSecondaryOrientation.getStartAfterPadding();
-            final int otherEnd = otherStart + mSecondaryOrientation.getDecoratedMeasurement(view);
+            final int otherStart;
+            final int otherEnd;
+            if (isLayoutRTL() && mOrientation == VERTICAL) {
+                otherEnd = lp.mFullSpan ? mSecondaryOrientation.getEndAfterPadding() :
+                        mSecondaryOrientation.getEndAfterPadding()
+                                - (mSpanCount - 1 - currentSpan.mIndex) * mSizePerSpan;
+                otherStart = otherEnd - mSecondaryOrientation.getDecoratedMeasurement(view);
+            } else {
+                otherStart = lp.mFullSpan ? mSecondaryOrientation.getStartAfterPadding()
+                        : currentSpan.mIndex * mSizePerSpan +
+                                mSecondaryOrientation.getStartAfterPadding();
+                otherEnd = otherStart + mSecondaryOrientation.getDecoratedMeasurement(view);
+            }
+
             if (mOrientation == VERTICAL) {
                 layoutDecoratedWithMargins(view, otherStart, start, otherEnd, end);
             } else {
@@ -1580,7 +1694,7 @@
     }
 
     private void recycle(RecyclerView.Recycler recycler, LayoutState layoutState) {
-        if (layoutState.mStopInFocusable) {
+        if (!layoutState.mRecycle || layoutState.mInfinite) {
             return;
         }
         if (layoutState.mAvailable == 0) {
@@ -1938,6 +2052,7 @@
             layoutDir = LAYOUT_START;
             referenceChildPosition = getFirstChildPosition();
         }
+        mLayoutState.mRecycle = true;
         updateLayoutState(referenceChildPosition, state);
         setLayoutStateDirection(layoutDir);
         mLayoutState.mCurrentPosition = referenceChildPosition + mLayoutState.mItemDirection;
@@ -2007,8 +2122,13 @@
 
     @Override
     public RecyclerView.LayoutParams generateDefaultLayoutParams() {
-        return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
-                ViewGroup.LayoutParams.WRAP_CONTENT);
+        if (mOrientation == HORIZONTAL) {
+            return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+                    ViewGroup.LayoutParams.FILL_PARENT);
+        } else {
+            return new LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
+                    ViewGroup.LayoutParams.WRAP_CONTENT);
+        }
     }
 
     @Override
@@ -2068,6 +2188,7 @@
         mLayoutState.mCurrentPosition = referenceChildPosition + mLayoutState.mItemDirection;
         mLayoutState.mAvailable = (int) (MAX_SCROLL_FACTOR * mPrimaryOrientation.getTotalSpace());
         mLayoutState.mStopInFocusable = true;
+        mLayoutState.mRecycle = false;
         fill(recycler, mLayoutState, state);
         mLastLayoutFromEnd = mShouldReverseLayout;
         if (!prevFocusFullSpan) {
diff --git a/v7/recyclerview/src/android/support/v7/widget/ViewInfoStore.java b/v7/recyclerview/src/android/support/v7/widget/ViewInfoStore.java
index 0af8dfb..69529f0 100644
--- a/v7/recyclerview/src/android/support/v7/widget/ViewInfoStore.java
+++ b/v7/recyclerview/src/android/support/v7/widget/ViewInfoStore.java
@@ -21,6 +21,7 @@
 import android.support.v4.util.ArrayMap;
 import android.support.v4.util.LongSparseArray;
 import android.support.v4.util.Pools;
+import android.view.View;
 
 import static android.support.v7.widget.RecyclerView.ViewHolder;
 import static android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo;
@@ -241,6 +242,10 @@
         InfoRecord.drainCache();
     }
 
+    public void onViewDetached(ViewHolder viewHolder) {
+        removeFromDisappearedInLayout(viewHolder);
+    }
+
     interface ProcessCallback {
         void processDisappeared(ViewHolder viewHolder, ItemHolderInfo preInfo,
                 @Nullable ItemHolderInfo postInfo);
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java
new file mode 100644
index 0000000..cfe6fe1
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java
@@ -0,0 +1,240 @@
+/*
+ * 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.view.View;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+
+import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
+import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.junit.Assert.assertEquals;
+
+public class BaseGridLayoutManagerTest extends BaseRecyclerViewInstrumentationTest {
+
+    static final String TAG = "GridLayoutManagerTest";
+    static final boolean DEBUG = false;
+
+    WrappedGridLayoutManager mGlm;
+    GridTestAdapter mAdapter;
+
+    public RecyclerView setupBasic(Config config) throws Throwable {
+        return setupBasic(config, new GridTestAdapter(config.mItemCount));
+    }
+
+    public RecyclerView setupBasic(Config config, GridTestAdapter testAdapter) throws Throwable {
+        RecyclerView recyclerView = new RecyclerView(getActivity());
+        mAdapter = testAdapter;
+        mGlm = new WrappedGridLayoutManager(getActivity(), config.mSpanCount, config.mOrientation,
+                config.mReverseLayout);
+        mAdapter.assignSpanSizeLookup(mGlm);
+        recyclerView.setAdapter(mAdapter);
+        recyclerView.setLayoutManager(mGlm);
+        return recyclerView;
+    }
+
+    public static List<Config> createBaseVariations() {
+        List<Config> variations = new ArrayList<>();
+        for (int orientation : new int[]{VERTICAL, HORIZONTAL}) {
+            for (boolean reverseLayout : new boolean[]{false, true}) {
+                for (int spanCount : new int[]{1, 3, 4}) {
+                    variations.add(new Config(spanCount, orientation, reverseLayout));
+                }
+            }
+        }
+        return variations;
+    }
+
+    public void waitForFirstLayout(RecyclerView recyclerView) throws Throwable {
+        mGlm.expectLayout(1);
+        setRecyclerView(recyclerView);
+        mGlm.waitForLayout(2);
+    }
+
+    protected int getSize(View view) {
+        if (mGlm.getOrientation() == GridLayoutManager.HORIZONTAL) {
+            return view.getWidth();
+        }
+        return view.getHeight();
+    }
+
+    GridLayoutManager.LayoutParams getLp(View view) {
+        return (GridLayoutManager.LayoutParams) view.getLayoutParams();
+    }
+
+    static class Config implements Cloneable {
+
+        int mSpanCount;
+        int mOrientation = GridLayoutManager.VERTICAL;
+        int mItemCount = 1000;
+        int mSpanPerItem = 1;
+        boolean mReverseLayout = false;
+
+        Config(int spanCount, int itemCount) {
+            mSpanCount = spanCount;
+            mItemCount = itemCount;
+        }
+
+        public Config(int spanCount, int orientation, boolean reverseLayout) {
+            mSpanCount = spanCount;
+            mOrientation = orientation;
+            mReverseLayout = reverseLayout;
+        }
+
+        Config orientation(int orientation) {
+            mOrientation = orientation;
+            return this;
+        }
+
+        @Override
+        public String toString() {
+            return "Config{" +
+                    "mSpanCount=" + mSpanCount +
+                    ", mOrientation=" + (mOrientation == GridLayoutManager.HORIZONTAL ? "h" : "v") +
+                    ", mItemCount=" + mItemCount +
+                    ", mReverseLayout=" + mReverseLayout +
+                    '}';
+        }
+
+        public Config reverseLayout(boolean reverseLayout) {
+            mReverseLayout = reverseLayout;
+            return this;
+        }
+
+        @Override
+        protected Object clone() throws CloneNotSupportedException {
+            return super.clone();
+        }
+    }
+
+    class WrappedGridLayoutManager extends GridLayoutManager {
+
+        CountDownLatch mLayoutLatch;
+
+        List<GridLayoutManagerTest.Callback>
+                mCallbacks = new ArrayList<GridLayoutManagerTest.Callback>();
+
+        Boolean mFakeRTL;
+
+        public WrappedGridLayoutManager(Context context, int spanCount) {
+            super(context, spanCount);
+        }
+
+        public WrappedGridLayoutManager(Context context, int spanCount, int orientation,
+                boolean reverseLayout) {
+            super(context, spanCount, orientation, reverseLayout);
+        }
+
+        @Override
+        protected boolean isLayoutRTL() {
+            return mFakeRTL == null ? super.isLayoutRTL() : mFakeRTL;
+        }
+
+        public void setFakeRtl(Boolean fakeRtl) {
+            mFakeRTL = fakeRtl;
+            try {
+                requestLayoutOnUIThread(mRecyclerView);
+            } catch (Throwable throwable) {
+                postExceptionToInstrumentation(throwable);
+            }
+        }
+
+        @Override
+        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+            try {
+                for (GridLayoutManagerTest.Callback callback : mCallbacks) {
+                    callback.onBeforeLayout(recycler, state);
+                }
+                super.onLayoutChildren(recycler, state);
+                for (GridLayoutManagerTest.Callback callback : mCallbacks) {
+                    callback.onAfterLayout(recycler, state);
+                }
+            } catch (Throwable t) {
+                postExceptionToInstrumentation(t);
+            }
+            mLayoutLatch.countDown();
+        }
+
+        @Override
+        LayoutState createLayoutState() {
+            return new LayoutState() {
+                @Override
+                View next(RecyclerView.Recycler recycler) {
+                    final boolean hadMore = hasMore(mRecyclerView.mState);
+                    final int position = mCurrentPosition;
+                    View next = super.next(recycler);
+                    assertEquals("if has more, should return a view", hadMore, next != null);
+                    assertEquals("position of the returned view must match current position",
+                            position, RecyclerView.getChildViewHolderInt(next).getLayoutPosition());
+                    return next;
+                }
+            };
+        }
+
+        public void expectLayout(int layoutCount) {
+            mLayoutLatch = new CountDownLatch(layoutCount);
+        }
+
+        public void waitForLayout(int seconds) throws InterruptedException {
+            mLayoutLatch.await(seconds, SECONDS);
+        }
+    }
+
+    class GridTestAdapter extends TestAdapter {
+
+        Set<Integer> mFullSpanItems = new HashSet<Integer>();
+        int mSpanPerItem = 1;
+
+        GridTestAdapter(int count) {
+            super(count);
+        }
+
+        GridTestAdapter(int count, int spanPerItem) {
+            super(count);
+            mSpanPerItem = spanPerItem;
+        }
+
+        void setFullSpan(int... items) {
+            for (int i : items) {
+                mFullSpanItems.add(i);
+            }
+        }
+
+        void assignSpanSizeLookup(final GridLayoutManager glm) {
+            glm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
+                @Override
+                public int getSpanSize(int position) {
+                    return mFullSpanItems.contains(position) ? glm.getSpanCount() : mSpanPerItem;
+                }
+            });
+        }
+    }
+
+    class Callback {
+
+        public void onBeforeLayout(RecyclerView.Recycler recycler, RecyclerView.State state) {
+        }
+
+        public void onAfterLayout(RecyclerView.Recycler recycler, RecyclerView.State state) {
+        }
+    }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseLinearLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseLinearLayoutManagerTest.java
index a8f554a..4e37369 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseLinearLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseLinearLayoutManagerTest.java
@@ -19,6 +19,7 @@
 import android.graphics.Rect;
 import android.util.Log;
 import android.view.View;
+import android.view.ViewGroup;
 
 import java.lang.reflect.Field;
 import java.util.ArrayList;
@@ -30,6 +31,8 @@
 
 import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
 import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
+import static android.view.ViewGroup.LayoutParams.FILL_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 import static org.junit.Assert.*;
 
 public class BaseLinearLayoutManagerTest extends BaseRecyclerViewInstrumentationTest {
@@ -42,7 +45,11 @@
         for (int orientation : new int[]{VERTICAL, HORIZONTAL}) {
             for (boolean reverseLayout : new boolean[]{false, true}) {
                 for (boolean stackFromBottom : new boolean[]{false, true}) {
-                    variations.add(new Config(orientation, reverseLayout, stackFromBottom));
+                    for (boolean wrap : new boolean[]{false, true}) {
+                        variations.add(
+                                new Config(orientation, reverseLayout, stackFromBottom).wrap(wrap));
+                    }
+
                 }
             }
         }
@@ -79,6 +86,14 @@
         mLayoutManager.setStackFromEnd(config.mStackFromEnd);
         mLayoutManager.setRecycleChildrenOnDetach(config.mRecycleChildrenOnDetach);
         mRecyclerView.setLayoutManager(mLayoutManager);
+        if (config.mWrap) {
+            mRecyclerView.setLayoutParams(
+                    new ViewGroup.LayoutParams(
+                            config.mOrientation == HORIZONTAL ? WRAP_CONTENT : FILL_PARENT,
+                            config.mOrientation == VERTICAL ? WRAP_CONTENT : FILL_PARENT
+                    )
+            );
+        }
         if (waitForFirstLayout) {
             waitForFirstLayout();
         }
@@ -248,6 +263,8 @@
 
         int mItemCount = DEFAULT_ITEM_COUNT;
 
+        boolean mWrap = false;
+
         TestAdapter mTestAdapter;
 
         Config(int orientation, boolean reverseLayout, boolean stackFromEnd) {
@@ -304,8 +321,14 @@
                     ", mReverseLayout=" + mReverseLayout +
                     ", mRecycleChildrenOnDetach=" + mRecycleChildrenOnDetach +
                     ", mItemCount=" + mItemCount +
+                    ", wrap=" + mWrap +
                     '}';
         }
+
+        public Config wrap(boolean wrap) {
+            mWrap = wrap;
+            return this;
+        }
     }
 
     class WrappedLinearLayoutManager extends LinearLayoutManager {
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewAnimationsTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewAnimationsTest.java
index dfac00a..b8fb198 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewAnimationsTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewAnimationsTest.java
@@ -35,7 +35,7 @@
  */
 public class BaseRecyclerViewAnimationsTest extends BaseRecyclerViewInstrumentationTest {
 
-    protected static final boolean DEBUG = false;
+    protected static final boolean DEBUG = true;
 
     protected static final String TAG = "RecyclerViewAnimationsTest";
 
@@ -645,6 +645,14 @@
             }
             setFrom(viewHolder);
         }
+
+        @Override
+        public String toString() {
+            return "LoggingInfo{" +
+                    "changeFlags=" + changeFlags +
+                    ", payloads=" + payloads +
+                    '}';
+        }
     }
 
     static class AnimateChange extends AnimateLogBase {
@@ -681,9 +689,9 @@
     }
     static class AnimateLogBase {
 
-        final RecyclerView.ViewHolder viewHolder;
-        final LoggingInfo preInfo;
-        final LoggingInfo postInfo;
+        public final RecyclerView.ViewHolder viewHolder;
+        public final LoggingInfo preInfo;
+        public final LoggingInfo postInfo;
 
         public AnimateLogBase(RecyclerView.ViewHolder viewHolder, LoggingInfo pre,
                 LoggingInfo postInfo) {
@@ -692,6 +700,14 @@
             this.postInfo = postInfo;
         }
 
+        public String log() {
+            return getClass().getSimpleName() + "[" +  log(preInfo) + " - " + log(postInfo) + "]";
+        }
+
+        public String log(LoggingInfo info) {
+            return info == null ? "null" : info.toString();
+        }
+
         @Override
         public boolean equals(Object o) {
             if (this == o) {
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
index a250f48..4acb3e5 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
@@ -541,7 +541,6 @@
                 assertEquals("getViewForPosition should return correct position",
                         i, getPosition(view));
                 addView(view);
-
                 measureChildWithMargins(view, 0, 0);
                 if (getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL) {
                     layoutDecorated(view, getWidth() - getDecoratedMeasuredWidth(view), top,
@@ -672,7 +671,7 @@
             assertNotNull(holder.mOwnerRecyclerView);
             assertEquals(position, holder.getAdapterPosition());
             final Item item = mItems.get(position);
-            ((TextView) (holder.itemView)).setText(item.mText + "(" + item.mAdapterIndex + ")");
+            ((TextView) (holder.itemView)).setText(item.mText + "(" + item.mId + ")");
             holder.mBoundItem = item;
         }
 
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseStaggeredGridLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseStaggeredGridLayoutManagerTest.java
index 6fe41f5..6519a32 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseStaggeredGridLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseStaggeredGridLayoutManagerTest.java
@@ -1,7 +1,5 @@
 package android.support.v7.widget;
 
-import org.junit.Before;
-
 import android.graphics.Rect;
 import android.support.annotation.Nullable;
 import android.util.Log;
@@ -45,8 +43,11 @@
                 for (int spanCount : new int[]{1, 3}) {
                     for (int gapStrategy : new int[]{GAP_HANDLING_NONE,
                             GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS}) {
-                        variations.add(new Config(orientation, reverseLayout, spanCount,
-                                gapStrategy));
+                        for (boolean wrap : new boolean[]{true, false}) {
+                            variations.add(new Config(orientation, reverseLayout, spanCount,
+                                    gapStrategy).wrap(wrap));
+                        }
+
                     }
                 }
             }
@@ -391,6 +392,8 @@
 
         int mItemCount = DEFAULT_ITEM_COUNT;
 
+        boolean mWrap = false;
+
         Config(int orientation, boolean reverseLayout, int spanCount, int gapStrategy) {
             mOrientation = orientation;
             mReverseLayout = reverseLayout;
@@ -427,6 +430,11 @@
             return this;
         }
 
+        public Config wrap(boolean wrap) {
+            mWrap = wrap;
+            return this;
+        }
+
         @Override
         public String toString() {
             return "[CONFIG:" +
@@ -434,6 +442,7 @@
                     " orientation:" + (mOrientation == HORIZONTAL ? "horz," : "vert,") +
                     " reverse:" + (mReverseLayout ? "T" : "F") +
                     " itemCount:" + mItemCount +
+                    " wrapContent:" + mWrap +
                     " gap strategy: " + gapStrategyName(mGapStrategy);
         }
 
@@ -789,11 +798,12 @@
             RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) holder.itemView
                     .getLayoutParams();
             if (lp instanceof StaggeredGridLayoutManager.LayoutParams) {
-                ((StaggeredGridLayoutManager.LayoutParams) lp).setFullSpan(mFullSpanItems.contains(item.mAdapterIndex));
+                ((StaggeredGridLayoutManager.LayoutParams) lp)
+                        .setFullSpan(mFullSpanItems.contains(item.mAdapterIndex));
             } else {
-                StaggeredGridLayoutManager.LayoutParams
-                        slp = new StaggeredGridLayoutManager.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
-                        ViewGroup.LayoutParams.WRAP_CONTENT);
+                StaggeredGridLayoutManager.LayoutParams slp
+                        = (StaggeredGridLayoutManager.LayoutParams) mLayoutManager
+                        .generateDefaultLayoutParams();
                 holder.itemView.setLayoutParams(slp);
                 slp.setFullSpan(mFullSpanItems.contains(item.mAdapterIndex));
                 lp = slp;
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseWrapContentTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseWrapContentTest.java
new file mode 100644
index 0000000..23aa5d5
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseWrapContentTest.java
@@ -0,0 +1,519 @@
+/*
+ * 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.content.Context;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.support.annotation.Nullable;
+import android.support.v4.util.LongSparseArray;
+import android.support.v7.widget.TestedFrameLayout.FullControlLayoutParams;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Class to test any generic wrap content behavior.
+ * It does so by running the same view scenario twice. Once with match parent setup to record all
+ * dimensions and once with wrap_content setup. Then compares all child locations & ids +
+ * RecyclerView size.
+ */
+abstract public class BaseWrapContentTest extends BaseRecyclerViewInstrumentationTest {
+
+    static final boolean DEBUG = false;
+    static final String TAG = "WrapContentTest";
+    RecyclerView.LayoutManager mLayoutManager;
+
+    TestAdapter mTestAdapter;
+
+    LoggingItemAnimator mLoggingItemAnimator;
+
+    boolean mIsWrapContent;
+
+    protected final WrapContentConfig mWrapContentConfig;
+
+    public BaseWrapContentTest(WrapContentConfig config) {
+        mWrapContentConfig = config;
+    }
+
+    abstract RecyclerView.LayoutManager createLayoutManager();
+
+    protected void testScenerio(Scenario scenario) throws Throwable {
+        FullControlLayoutParams matchParent = new FullControlLayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT);
+        FullControlLayoutParams wrapContent = new FullControlLayoutParams(
+                ViewGroup.LayoutParams.WRAP_CONTENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT);
+        if (mWrapContentConfig.isUnlimitedHeight()) {
+            wrapContent.hSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+        }
+        if (mWrapContentConfig.isUnlimitedWidth()) {
+            wrapContent.wSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+        }
+
+        mIsWrapContent = false;
+        List<Snapshot> s1 = runScenario(scenario, matchParent, null);
+        mIsWrapContent = true;
+
+        List<Snapshot> s2 = runScenario(scenario, wrapContent, s1);
+        assertEquals("test sanity", s1.size(), s2.size());
+
+        for (int i = 0; i < s1.size(); i++) {
+            Snapshot step1 = s1.get(i);
+            Snapshot step2 = s2.get(i);
+            step1.assertSame(step2, i);
+        }
+    }
+
+    public List<Snapshot> runScenario(Scenario scenario, ViewGroup.LayoutParams lp,
+            @Nullable List<Snapshot> compareWith)
+            throws Throwable {
+        removeRecyclerView();
+        Item.idCounter.set(0);
+        List<Snapshot> result = new ArrayList<>();
+        RecyclerView.LayoutManager layoutManager = scenario.createLayoutManager();
+        WrappedRecyclerView recyclerView = new WrappedRecyclerView(getActivity());
+        recyclerView.setBackgroundColor(Color.rgb(0, 0, 255));
+        recyclerView.setLayoutManager(layoutManager);
+        recyclerView.setLayoutParams(lp);
+        mLayoutManager = layoutManager;
+        mTestAdapter = new TestAdapter(scenario.getSeedAdapterSize());
+        recyclerView.setAdapter(mTestAdapter);
+        mLoggingItemAnimator = new LoggingItemAnimator();
+        recyclerView.setItemAnimator(mLoggingItemAnimator);
+        setRecyclerView(recyclerView);
+        recyclerView.waitUntilLayout();
+        int stepIndex = 0;
+        for (Step step : scenario.mStepList) {
+            mLoggingItemAnimator.reset();
+            step.onRun();
+            recyclerView.waitUntilLayout();
+            recyclerView.waitUntilAnimations();
+            Snapshot snapshot = takeSnapshot();
+            if (mIsWrapContent) {
+                snapshot.assertRvSize();
+            }
+            result.add(snapshot);
+            if (compareWith != null) {
+                compareWith.get(stepIndex).assertSame(snapshot, stepIndex);
+            }
+            stepIndex++;
+        }
+        recyclerView.waitUntilLayout();
+        recyclerView.waitUntilAnimations();
+        Snapshot snapshot = takeSnapshot();
+        if (mIsWrapContent) {
+            snapshot.assertRvSize();
+        }
+        result.add(snapshot);
+        if (compareWith != null) {
+            compareWith.get(stepIndex).assertSame(snapshot, stepIndex);
+        }
+        return result;
+    }
+
+    void layoutAndCheck(TestedFrameLayout.FullControlLayoutParams lp,
+            BaseWrapContentWithAspectRatioTest.WrapContentAdapter adapter, Rect[] expected,
+            int width, int height) throws Throwable {
+        WrappedRecyclerView recyclerView = new WrappedRecyclerView(getActivity());
+        recyclerView.setBackgroundColor(Color.rgb(0, 0, 255));
+        recyclerView.setLayoutManager(createLayoutManager());
+        recyclerView.setAdapter(adapter);
+        recyclerView.setLayoutParams(lp);
+        setRecyclerView(recyclerView);
+        recyclerView.waitUntilLayout();
+        Snapshot snapshot = takeSnapshot();
+        int index = 0;
+        for (BaseWrapContentWithAspectRatioTest.MeasureBehavior behavior : adapter.behaviors) {
+            assertThat("behavior " + index, snapshot.mChildCoordinates.get(behavior.getId()),
+                    is(expected[index]));
+            index ++;
+        }
+        Rect boundingBox = new Rect(0, 0, 0, 0);
+        for (Rect rect : expected) {
+            boundingBox.union(rect);
+        }
+        assertThat(recyclerView.getWidth(), is(width));
+        assertThat(recyclerView.getHeight(), is(height));
+    }
+
+
+    abstract protected int getVerticalGravity(RecyclerView.LayoutManager layoutManager);
+
+    abstract protected int getHorizontalGravity(RecyclerView.LayoutManager layoutManager);
+
+    protected Snapshot takeSnapshot() throws Throwable {
+        Snapshot snapshot = new Snapshot(mRecyclerView, mLoggingItemAnimator,
+                getHorizontalGravity(mLayoutManager), getVerticalGravity(mLayoutManager));
+        return snapshot;
+    }
+
+    abstract class Scenario {
+
+        ArrayList<Step> mStepList = new ArrayList<>();
+
+        public Scenario(Step... steps) {
+            Collections.addAll(mStepList, steps);
+        }
+
+        public int getSeedAdapterSize() {
+            return 10;
+        }
+
+        public RecyclerView.LayoutManager createLayoutManager() {
+            return BaseWrapContentTest.this.createLayoutManager();
+        }
+    }
+
+    abstract static class Step {
+
+        abstract void onRun() throws Throwable;
+    }
+
+    class Snapshot {
+
+        Rect mRawChildrenBox = new Rect();
+
+        Rect mRvSize = new Rect();
+
+        Rect mRvPadding = new Rect();
+
+        Rect mRvParentSize = new Rect();
+
+        LongSparseArray<Rect> mChildCoordinates = new LongSparseArray<>();
+
+        LongSparseArray<String> mAppear = new LongSparseArray<>();
+
+        LongSparseArray<String> mDisappear = new LongSparseArray<>();
+
+        LongSparseArray<String> mPersistent = new LongSparseArray<>();
+
+        LongSparseArray<String> mChanged = new LongSparseArray<>();
+
+        int mVerticalGravity;
+
+        int mHorizontalGravity;
+
+        int mOffsetX, mOffsetY;// how much we should offset children
+
+        public Snapshot(RecyclerView recyclerView, LoggingItemAnimator loggingItemAnimator,
+                int horizontalGravity, int verticalGravity)
+                throws Throwable {
+            mRvSize = getViewBounds(recyclerView);
+            mRvParentSize = getViewBounds((View) recyclerView.getParent());
+            mRvPadding = new Rect(recyclerView.getPaddingLeft(), recyclerView.getPaddingTop(),
+                    recyclerView.getPaddingRight(), recyclerView.getPaddingBottom());
+            mVerticalGravity = verticalGravity;
+            mHorizontalGravity = horizontalGravity;
+            if (mVerticalGravity == Gravity.TOP) {
+                mOffsetY = 0;
+            } else {
+                mOffsetY = mRvParentSize.bottom - mRvSize.bottom;
+            }
+
+            if (mHorizontalGravity == Gravity.LEFT) {
+                mOffsetX = 0;
+            } else {
+                mOffsetX = mRvParentSize.right - mRvSize.right;
+            }
+            collectChildCoordinates(recyclerView);
+            if (loggingItemAnimator != null) {
+                collectInto(mAppear, loggingItemAnimator.mAnimateAppearanceList);
+                collectInto(mDisappear, loggingItemAnimator.mAnimateDisappearanceList);
+                collectInto(mPersistent, loggingItemAnimator.mAnimatePersistenceList);
+                collectInto(mChanged, loggingItemAnimator.mAnimateChangeList);
+            }
+        }
+
+        public boolean doesChildrenFitVertically() {
+            return mRawChildrenBox.top >= mRvPadding.top
+                    && mRawChildrenBox.bottom <= mRvSize.bottom - mRvPadding.bottom;
+        }
+
+        public boolean doesChildrenFitHorizontally() {
+            return mRawChildrenBox.left >= mRvPadding.left
+                    && mRawChildrenBox.right <= mRvSize.right - mRvPadding.right;
+        }
+
+        public void assertSame(Snapshot other, int step) {
+            if (mWrapContentConfig.isUnlimitedHeight() &&
+                    (!doesChildrenFitVertically() || !other.doesChildrenFitVertically())) {
+                if (DEBUG) {
+                    Log.d(TAG, "cannot assert coordinates because it does not fit vertically");
+                }
+                return;
+            }
+            if (mWrapContentConfig.isUnlimitedWidth() &&
+                    (!doesChildrenFitHorizontally() || !other.doesChildrenFitHorizontally())) {
+                if (DEBUG) {
+                    Log.d(TAG, "cannot assert coordinates because it does not fit horizontally");
+                }
+                return;
+            }
+            assertMap("child coordinates. step:" + step, mChildCoordinates,
+                    other.mChildCoordinates);
+            if (mWrapContentConfig.isUnlimitedHeight() || mWrapContentConfig.isUnlimitedWidth()) {
+                return;//cannot assert animatinos in unlimited size
+            }
+            assertMap("appearing step:" + step, mAppear, other.mAppear);
+            assertMap("disappearing step:" + step, mDisappear, other.mDisappear);
+            assertMap("persistent step:" + step, mPersistent, other.mPersistent);
+            assertMap("changed step:" + step, mChanged, other.mChanged);
+        }
+
+        private void assertMap(String prefix, LongSparseArray<?> map1, LongSparseArray<?> map2) {
+            StringBuilder logBuilder = new StringBuilder();
+            logBuilder.append(prefix).append("\n");
+            logBuilder.append("map1").append("\n");
+            logInto(map1, logBuilder);
+            logBuilder.append("map2").append("\n");
+            logInto(map2, logBuilder);
+            final String log = logBuilder.toString();
+            assertEquals(log + " same size", map1.size(), map2.size());
+            for (int i = 0; i < map1.size(); i++) {
+                assertAtIndex(log, map1, map2, i);
+            }
+        }
+
+        private void assertAtIndex(String prefix, LongSparseArray<?> map1, LongSparseArray<?> map2,
+                int index) {
+            long key1 = map1.keyAt(index);
+            long key2 = map2.keyAt(index);
+            assertEquals(prefix + "key mismatch at index " + index, key1, key2);
+            Object value1 = map1.valueAt(index);
+            Object value2 = map2.valueAt(index);
+            assertEquals(prefix + " value mismatch at index " + index, value1, value2);
+        }
+
+        private void logInto(LongSparseArray<?> map, StringBuilder sb) {
+            for (int i = 0; i < map.size(); i++) {
+                long key = map.keyAt(i);
+                Object value = map.valueAt(i);
+                sb.append(key).append(" : ").append(value).append("\n");
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder("Snapshot{\n");
+            sb.append("child coordinates:\n");
+            logInto(mChildCoordinates, sb);
+            sb.append("appear animations:\n");
+            logInto(mAppear, sb);
+            sb.append("disappear animations:\n");
+            logInto(mDisappear, sb);
+            sb.append("change animations:\n");
+            logInto(mChanged, sb);
+            sb.append("persistent animations:\n");
+            logInto(mPersistent, sb);
+            sb.append("}");
+            return sb.toString();
+        }
+
+        @Override
+        public int hashCode() {
+            int result = mChildCoordinates.hashCode();
+            result = 31 * result + mAppear.hashCode();
+            result = 31 * result + mDisappear.hashCode();
+            result = 31 * result + mPersistent.hashCode();
+            result = 31 * result + mChanged.hashCode();
+            return result;
+        }
+
+        private void collectInto(
+                LongSparseArray<String> target,
+                List<? extends BaseRecyclerViewAnimationsTest.AnimateLogBase> list) {
+            for (BaseRecyclerViewAnimationsTest.AnimateLogBase base : list) {
+                long id = getItemId(base.viewHolder);
+                assertNull(target.get(id));
+                target.put(id, log(base));
+            }
+        }
+
+        private String log(BaseRecyclerViewAnimationsTest.AnimateLogBase base) {
+            return base.getClass().getSimpleName() +
+                    ((TextView) base.viewHolder.itemView).getText() + ": " +
+                    "[pre:" + log(base.postInfo) +
+                    ", post:" + log(base.postInfo) + "]";
+        }
+
+        private String log(BaseRecyclerViewAnimationsTest.LoggingInfo postInfo) {
+            if (postInfo == null) {
+                return "?";
+            }
+            return "PI[flags: " + postInfo.changeFlags
+                    + ",l:" + (postInfo.left + mOffsetX)
+                    + ",t:" + (postInfo.top + mOffsetY)
+                    + ",r:" + (postInfo.right + mOffsetX)
+                    + ",b:" + (postInfo.bottom + mOffsetY) + "]";
+        }
+
+        void collectChildCoordinates(RecyclerView recyclerView) throws Throwable {
+            mRawChildrenBox = new Rect(0, 0, 0, 0);
+            final int childCount = recyclerView.getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                View child = recyclerView.getChildAt(i);
+                Rect childBounds = getChildBounds(recyclerView, child, true);
+                mRawChildrenBox.union(getChildBounds(recyclerView, child, false));
+                RecyclerView.ViewHolder childViewHolder = recyclerView.getChildViewHolder(child);
+                mChildCoordinates.put(getItemId(childViewHolder), childBounds);
+            }
+        }
+
+        private Rect getViewBounds(View view) {
+            return new Rect(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
+        }
+
+        private Rect getChildBounds(RecyclerView recyclerView, View child, boolean offset) {
+            RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
+            RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
+            Rect rect = new Rect(layoutManager.getDecoratedLeft(child) - lp.leftMargin,
+                    layoutManager.getDecoratedTop(child) - lp.topMargin,
+                    layoutManager.getDecoratedRight(child) + lp.rightMargin,
+                    layoutManager.getDecoratedBottom(child) + lp.bottomMargin);
+            if (offset) {
+                rect.offset(mOffsetX, mOffsetY);
+            }
+            return rect;
+        }
+
+        private long getItemId(RecyclerView.ViewHolder vh) {
+            if (vh instanceof TestViewHolder) {
+                return ((TestViewHolder) vh).mBoundItem.mId;
+            } else if (vh instanceof BaseWrapContentWithAspectRatioTest.WrapContentViewHolder) {
+                BaseWrapContentWithAspectRatioTest.WrapContentViewHolder casted =
+                        (BaseWrapContentWithAspectRatioTest.WrapContentViewHolder) vh;
+                return casted.mView.mBehavior.getId();
+            } else {
+                throw new IllegalArgumentException("i don't support any VH");
+            }
+        }
+
+        public void assertRvSize() {
+            if (shouldWrapContentHorizontally()) {
+                int expectedW = mRawChildrenBox.width() + mRvPadding.left + mRvPadding.right;
+                assertTrue(mRvSize.width() + " <= " + expectedW, mRvSize.width() <= expectedW);
+            }
+            if (shouldWrapContentVertically()) {
+                int expectedH = mRawChildrenBox.height() + mRvPadding.top + mRvPadding.bottom;
+                assertTrue(mRvSize.height() + "<=" + expectedH, mRvSize.height() <= expectedH);
+            }
+        }
+    }
+
+    protected boolean shouldWrapContentHorizontally() {
+        return true;
+    }
+
+    protected boolean shouldWrapContentVertically() {
+        return true;
+    }
+
+    static class WrappedRecyclerView extends RecyclerView {
+
+        public WrappedRecyclerView(Context context) {
+            super(context);
+        }
+
+        public void waitUntilLayout() {
+            while (isLayoutRequested()) {
+                try {
+                    Thread.sleep(100);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+
+        public void waitUntilAnimations() {
+            while (mItemAnimator != null && mItemAnimator.isRunning()) {
+                try {
+                    Thread.sleep(100);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+
+        @Override
+        protected void onLayout(boolean changed, int l, int t, int r, int b) {
+            super.onLayout(changed, l, t, r, b);
+        }
+    }
+
+    static class WrapContentConfig {
+
+        private boolean unlimitedWidth;
+        private boolean unlimitedHeight;
+
+        public WrapContentConfig(boolean unlimitedWidth, boolean unlimitedHeight) {
+            this.unlimitedWidth = unlimitedWidth;
+            this.unlimitedHeight = unlimitedHeight;
+        }
+
+        public boolean isUnlimitedWidth() {
+            return unlimitedWidth;
+        }
+
+        public WrapContentConfig setUnlimitedWidth(boolean unlimitedWidth) {
+            this.unlimitedWidth = unlimitedWidth;
+            return this;
+        }
+
+        public boolean isUnlimitedHeight() {
+            return unlimitedHeight;
+        }
+
+        public WrapContentConfig setUnlimitedHeight(boolean unlimitedHeight) {
+            this.unlimitedHeight = unlimitedHeight;
+            return this;
+        }
+
+        @Override
+        public String toString() {
+            return "WrapContentConfig{" +
+                    "unlimitedWidth=" + unlimitedWidth +
+                    ", unlimitedHeight=" + unlimitedHeight +
+                    '}';
+        }
+
+        public TestedFrameLayout.FullControlLayoutParams toLayoutParams(int wDim, int hDim) {
+            TestedFrameLayout.FullControlLayoutParams
+                    lp = new TestedFrameLayout.FullControlLayoutParams(
+                    wDim, hDim);
+            if (unlimitedWidth) {
+                lp.wSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+            }
+            if (unlimitedHeight) {
+                lp.hSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+            }
+            return lp;
+        }
+    }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseWrapContentWithAspectRatioTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseWrapContentWithAspectRatioTest.java
new file mode 100644
index 0000000..f022eee
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseWrapContentWithAspectRatioTest.java
@@ -0,0 +1,300 @@
+/*
+ * 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 org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.support.v4.util.Pair;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
+import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
+
+abstract public class BaseWrapContentWithAspectRatioTest extends BaseRecyclerViewInstrumentationTest {
+    final BaseWrapContentTest.WrapContentConfig mWrapContentConfig;
+
+    protected BaseWrapContentWithAspectRatioTest(
+            BaseWrapContentTest.WrapContentConfig wrapContentConfig) {
+        mWrapContentConfig = wrapContentConfig;
+    }
+
+    int getSize(View view, int orientation) {
+        if (orientation == VERTICAL) {
+            return view.getHeight();
+        }
+        return view.getWidth();
+    }
+
+    static class LoggingView extends View {
+
+        MeasureBehavior mBehavior;
+
+        public void setBehavior(MeasureBehavior behavior) {
+            mBehavior = behavior;
+        }
+
+        public LoggingView(Context context) {
+            super(context);
+        }
+
+        public LoggingView(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+
+        public LoggingView(Context context, AttributeSet attrs, int defStyleAttr) {
+            super(context, attrs, defStyleAttr);
+        }
+
+        public LoggingView(Context context, AttributeSet attrs, int defStyleAttr,
+                int defStyleRes) {
+            super(context, attrs, defStyleAttr, defStyleRes);
+        }
+
+        @Override
+        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+            mBehavior.onMeasure(this, widthMeasureSpec, heightMeasureSpec);
+        }
+
+        @Override
+        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+            super.onLayout(changed, left, top, right, bottom);
+            mBehavior.onLayout(changed, left, top, right, bottom);
+        }
+
+        public void setMeasured(int w, int h) {
+            setMeasuredDimension(w, h);
+        }
+
+        public void prepareLayoutParams() {
+            mBehavior.setLayoutParams(this);
+        }
+    }
+
+    static class AspectRatioMeasureBehavior extends MeasureBehavior {
+
+        Float ratio;
+        int control;
+
+        public AspectRatioMeasureBehavior(int desiredW, int desiredH, int wMode, int hMode) {
+            super(desiredW, desiredH, wMode, hMode);
+        }
+
+        public AspectRatioMeasureBehavior aspectRatio(int control, float ratio) {
+            this.control = control;
+            this.ratio = ratio;
+            return this;
+        }
+
+        @Override
+        public void onMeasure(LoggingView view, int wSpec,
+                int hSpec) {
+            super.onMeasure(view, wSpec, hSpec);
+            if (control == VERTICAL) {
+                view.setMeasured(getSecondary(view.getMeasuredHeight()),
+                        view.getMeasuredHeight());
+            } else if (control == HORIZONTAL) {
+                view.setMeasured(view.getMeasuredWidth(),
+                        getSecondary(view.getMeasuredWidth()));
+            }
+        }
+
+        public int getSecondary(int controlSize) {
+            return (int) (controlSize * ratio);
+        }
+    }
+
+    static class MeasureBehavior {
+        private static final AtomicLong idCounter = new AtomicLong(0);
+        public List<Pair<Integer, Integer>> measureSpecs = new ArrayList<>();
+        public List<Pair<Integer, Integer>> layouts = new ArrayList<>();
+        int desiredW, desiredH;
+        final long mId = idCounter.incrementAndGet();
+
+        ViewGroup.LayoutParams layoutParams;
+
+        public MeasureBehavior(int desiredW, int desiredH, int wMode, int hMode) {
+            this.desiredW = desiredW;
+            this.desiredH = desiredH;
+            layoutParams = new ViewGroup.LayoutParams(
+                    wMode, hMode
+            );
+        }
+
+        public long getId() {
+            return mId;
+        }
+
+        public void onMeasure(LoggingView view, int wSpec, int hSpec) {
+            measureSpecs.add(new Pair<>(wSpec, hSpec));
+            view.setMeasured(
+                    RecyclerView.LayoutManager.chooseSize(wSpec, desiredW, 0),
+                    RecyclerView.LayoutManager.chooseSize(hSpec, desiredH, 0));
+        }
+
+        public int getSpec(int position, int orientation) {
+            if (orientation == VERTICAL) {
+                return measureSpecs.get(position).second;
+            } else {
+                return measureSpecs.get(position).first;
+            }
+        }
+
+        public void setLayoutParams(LoggingView view) {
+            view.setLayoutParams(layoutParams);
+        }
+
+        public void onLayout(boolean changed, int left, int top, int right, int bottom) {
+            if (changed) {
+                layouts.add(new Pair<>(right - left, bottom - top));
+            }
+        }
+    }
+
+
+    static class WrapContentViewHolder extends RecyclerView.ViewHolder {
+
+        LoggingView mView;
+
+        public WrapContentViewHolder(ViewGroup parent) {
+            super(new LoggingView(parent.getContext()));
+            mView = (LoggingView) itemView;
+            mView.setBackgroundColor(Color.GREEN);
+        }
+    }
+
+    static class WrapContentAdapter extends RecyclerView.Adapter<WrapContentViewHolder> {
+
+        List<MeasureBehavior> behaviors = new ArrayList<>();
+
+        public WrapContentAdapter(MeasureBehavior... behaviors) {
+            Collections.addAll(this.behaviors, behaviors);
+        }
+
+        @Override
+        public WrapContentViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+            return new WrapContentViewHolder(parent);
+        }
+
+        @Override
+        public void onBindViewHolder(WrapContentViewHolder holder, int position) {
+            holder.mView.setBehavior(behaviors.get(position));
+            holder.mView.prepareLayoutParams();
+        }
+
+        @Override
+        public int getItemCount() {
+            return behaviors.size();
+        }
+    }
+
+    static class MeasureSpecMatcher extends BaseMatcher<Integer> {
+
+        private boolean checkSize = false;
+        private boolean checkMode = false;
+        private int mSize;
+        private int mMode;
+
+        public static MeasureSpecMatcher is(int size, int mode) {
+            MeasureSpecMatcher matcher = new MeasureSpecMatcher(size, mode);
+            matcher.checkSize = true;
+            matcher.checkMode = true;
+            return matcher;
+        }
+
+        public static MeasureSpecMatcher size(int size) {
+            MeasureSpecMatcher matcher = new MeasureSpecMatcher(size, 0);
+            matcher.checkSize = true;
+            matcher.checkMode = false;
+            return matcher;
+        }
+
+        public static MeasureSpecMatcher mode(int mode) {
+            MeasureSpecMatcher matcher = new MeasureSpecMatcher(0, mode);
+            matcher.checkSize = false;
+            matcher.checkMode = true;
+            return matcher;
+        }
+
+        private MeasureSpecMatcher(int size, int mode) {
+            mSize = size;
+            mMode = mode;
+
+        }
+
+        @Override
+        public boolean matches(Object item) {
+            if (item == null) {
+                return false;
+            }
+            Integer intValue = (Integer) item;
+            final int size = View.MeasureSpec.getSize(intValue);
+            final int mode = View.MeasureSpec.getMode(intValue);
+            if (checkSize && size != mSize) {
+                return false;
+            }
+            if (checkMode && mode != mMode) {
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public void describeMismatch(Object item, Description description) {
+            Integer intValue = (Integer) item;
+            final int size = View.MeasureSpec.getSize(intValue);
+            final int mode = View.MeasureSpec.getMode(intValue);
+            if (checkSize && size != mSize) {
+                description.appendText(" Expected size was ").appendValue(mSize)
+                        .appendText(" but received size is ").appendValue(size);
+            }
+            if (checkMode && mode != mMode) {
+                description.appendText(" Expected mode was ").appendValue(modeName(mMode))
+                        .appendText(" but received mode is ").appendValue(modeName(mode));
+            }
+        }
+
+        @Override
+        public void describeTo(Description description) {
+            if (checkSize) {
+                description.appendText(" Measure spec size:").appendValue(mSize);
+            }
+            if (checkMode) {
+                description.appendText(" Measure spec mode:").appendValue(modeName(mMode));
+            }
+        }
+
+        private static String modeName(int mode) {
+            switch (mode) {
+                case View.MeasureSpec.AT_MOST:
+                    return "at most";
+                case View.MeasureSpec.EXACTLY:
+                    return "exactly";
+                default:
+                    return "unspecified";
+            }
+        }
+    }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerBaseConfigSetTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerBaseConfigSetTest.java
new file mode 100644
index 0000000..99ec4ed
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerBaseConfigSetTest.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import android.util.Log;
+import android.view.View;
+
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(Parameterized.class)
+public class GridLayoutManagerBaseConfigSetTest extends BaseGridLayoutManagerTest {
+    @Parameterized.Parameters(name = "{0}")
+    public static List<Config> params() {
+        return createBaseVariations();
+    }
+
+    private final Config mConfig;
+
+    public GridLayoutManagerBaseConfigSetTest(Config config) {
+        mConfig = config;
+    }
+
+    @Test
+    public void scrollBackAndPreservePositions() throws Throwable {
+        Config config = (Config) mConfig.clone();
+        config.mItemCount = 150;
+        scrollBackAndPreservePositionsTest(config);
+    }
+
+    public void scrollBackAndPreservePositionsTest(final Config config) throws Throwable {
+        final RecyclerView rv = setupBasic(config);
+        for (int i = 1; i < mAdapter.getItemCount(); i += config.mSpanCount + 2) {
+            mAdapter.setFullSpan(i);
+        }
+        waitForFirstLayout(rv);
+        final int[] globalPositions = new int[mAdapter.getItemCount()];
+        Arrays.fill(globalPositions, Integer.MIN_VALUE);
+        final int scrollStep = (mGlm.mOrientationHelper.getTotalSpace() / 20)
+                * (config.mReverseLayout ? -1 : 1);
+        final String logPrefix = config.toString();
+        final int[] globalPos = new int[1];
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                assertSame("test sanity", mRecyclerView, rv);
+                int globalScrollPosition = 0;
+                int visited = 0;
+                while (visited < mAdapter.getItemCount()) {
+                    for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
+                        View child = mRecyclerView.getChildAt(i);
+                        final int pos = mRecyclerView.getChildLayoutPosition(child);
+                        if (globalPositions[pos] != Integer.MIN_VALUE) {
+                            continue;
+                        }
+                        visited++;
+                        GridLayoutManager.LayoutParams lp = (GridLayoutManager.LayoutParams)
+                                child.getLayoutParams();
+                        if (config.mReverseLayout) {
+                            globalPositions[pos] = globalScrollPosition +
+                                    mGlm.mOrientationHelper.getDecoratedEnd(child);
+                        } else {
+                            globalPositions[pos] = globalScrollPosition +
+                                    mGlm.mOrientationHelper.getDecoratedStart(child);
+                        }
+                        assertEquals(logPrefix + " span index should match",
+                                mGlm.getSpanSizeLookup().getSpanIndex(pos, mGlm.getSpanCount()),
+                                lp.getSpanIndex());
+                    }
+                    int scrolled = mGlm.scrollBy(scrollStep,
+                            mRecyclerView.mRecycler, mRecyclerView.mState);
+                    globalScrollPosition += scrolled;
+                    if (scrolled == 0) {
+                        assertEquals(
+                                logPrefix + " If scroll is complete, all views should be visited",
+                                visited, mAdapter.getItemCount());
+                    }
+                }
+                if (DEBUG) {
+                    Log.d(TAG, "done recording positions " + Arrays.toString(globalPositions));
+                }
+                globalPos[0] = globalScrollPosition;
+            }
+        });
+        checkForMainThreadException();
+        // test sanity, ensure scroll happened
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                final int childCount = mGlm.getChildCount();
+                final BitSet expectedPositions = new BitSet();
+                for (int i = 0; i < childCount; i ++) {
+                    expectedPositions.set(mAdapter.getItemCount() - i - 1);
+                }
+                for (int i = 0; i <childCount; i ++) {
+                    final View view = mGlm.getChildAt(i);
+                    int position = mGlm.getPosition(view);
+                    assertTrue("child position should be in last page", expectedPositions.get(position));
+                }
+            }
+        });
+        getInstrumentation().waitForIdleSync();
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                int globalScrollPosition = globalPos[0];
+                // now scroll back and make sure global positions match
+                BitSet shouldTest = new BitSet(mAdapter.getItemCount());
+                shouldTest.set(0, mAdapter.getItemCount() - 1, true);
+                String assertPrefix = config
+                        + " global pos must match when scrolling in reverse for position ";
+                int scrollAmount = Integer.MAX_VALUE;
+                while (!shouldTest.isEmpty() && scrollAmount != 0) {
+                    for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
+                        View child = mRecyclerView.getChildAt(i);
+                        int pos = mRecyclerView.getChildLayoutPosition(child);
+                        if (!shouldTest.get(pos)) {
+                            continue;
+                        }
+                        GridLayoutManager.LayoutParams lp = (GridLayoutManager.LayoutParams)
+                                child.getLayoutParams();
+                        shouldTest.clear(pos);
+                        int globalPos;
+                        if (config.mReverseLayout) {
+                            globalPos = globalScrollPosition +
+                                    mGlm.mOrientationHelper.getDecoratedEnd(child);
+                        } else {
+                            globalPos = globalScrollPosition +
+                                    mGlm.mOrientationHelper.getDecoratedStart(child);
+                        }
+                        assertEquals(assertPrefix + pos,
+                                globalPositions[pos], globalPos);
+                        assertEquals("span index should match",
+                                mGlm.getSpanSizeLookup().getSpanIndex(pos, mGlm.getSpanCount()),
+                                lp.getSpanIndex());
+                    }
+                    scrollAmount = mGlm.scrollBy(-scrollStep,
+                            mRecyclerView.mRecycler, mRecyclerView.mState);
+                    globalScrollPosition += scrollAmount;
+                }
+                assertTrue("all views should be seen", shouldTest.isEmpty());
+            }
+        });
+        checkForMainThreadException();
+    }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerCachedBordersTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerCachedBordersTest.java
new file mode 100644
index 0000000..2fc5015
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerCachedBordersTest.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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import android.view.View;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
+import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
+import static org.junit.Assert.assertEquals;
+
+@RunWith(Parameterized.class)
+public class GridLayoutManagerCachedBordersTest extends BaseGridLayoutManagerTest {
+
+    @Parameterized.Parameters(name = "{0}")
+    public static List<Config> params() {
+        List<Config> testConfigurations = createBaseVariations();
+        testConfigurations.addAll(cachedBordersTestConfigs());
+        return testConfigurations;
+    }
+
+    private final Config mConfig;
+
+    public GridLayoutManagerCachedBordersTest(Config config) {
+        mConfig = config;
+    }
+
+
+    @Test
+    public void gridCachedBorderstTest() throws Throwable {
+        RecyclerView recyclerView = setupBasic(mConfig);
+        waitForFirstLayout(recyclerView);
+        final boolean vertical = mConfig.mOrientation == GridLayoutManager.VERTICAL;
+        final int expectedSizeSum = vertical ? recyclerView.getWidth() : recyclerView.getHeight();
+        final int lastVisible = mGlm.findLastVisibleItemPosition();
+        for (int i = 0; i < lastVisible; i += mConfig.mSpanCount) {
+            if ((i + 1) * mConfig.mSpanCount - 1 < lastVisible) {
+                int childrenSizeSum = 0;
+                for (int j = 0; j < mConfig.mSpanCount; j++) {
+                    View child = recyclerView.getChildAt(i * mConfig.mSpanCount + j);
+                    childrenSizeSum += vertical ? child.getWidth() : child.getHeight();
+                }
+                assertEquals(expectedSizeSum, childrenSizeSum);
+            }
+        }
+    }
+
+    private static List<Config> cachedBordersTestConfigs() {
+        ArrayList<Config> configs = new ArrayList<Config>();
+        final int[] spanCounts = new int[]{88, 279, 741};
+        final int[] spanPerItem = new int[]{11, 9, 13};
+        for (int orientation : new int[]{VERTICAL, HORIZONTAL}) {
+            for (boolean reverseLayout : new boolean[]{false, true}) {
+                for (int i = 0; i < spanCounts.length; i++) {
+                    Config config = new Config(spanCounts[i], orientation, reverseLayout);
+                    config.mSpanPerItem = spanPerItem[i];
+                    configs.add(config);
+                }
+            }
+        }
+        return configs;
+    }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerRtlTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerRtlTest.java
new file mode 100644
index 0000000..78f1576
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerRtlTest.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.v7.widget;
+
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
+import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(Parameterized.class)
+public class GridLayoutManagerRtlTest extends BaseGridLayoutManagerTest {
+
+    public GridLayoutManagerRtlTest(Config config, boolean changeRtlAfter, boolean oneLine,
+            boolean itemsWrapContent) {
+        mConfig = config;
+        mChangeRtlAfter = changeRtlAfter;
+        mOneLine = oneLine;
+        mItemsWrapContent = itemsWrapContent;
+    }
+
+    @Parameterized.Parameters(name = "conf: {0} changeRl:{1} oneLine: {2} itemsWrap: {3}")
+    public static List<Object[]> params() {
+        List<Object[]> result = new ArrayList<>();
+        for (boolean changeRtlAfter : new boolean[]{false, true}) {
+            for (boolean oneLine : new boolean[]{false, true}) {
+                for (boolean itemsWrapContent : new boolean[]{false, true}) {
+                    for (Config config : createBaseVariations()) {
+                        result.add(new Object[] {
+                                config,
+                                changeRtlAfter,
+                                oneLine,
+                                itemsWrapContent
+                        });
+                    }
+                }
+            }
+        }
+        return result;
+    }
+    final Config mConfig;
+    final boolean mChangeRtlAfter;
+    final boolean mOneLine;
+    final boolean mItemsWrapContent;
+
+
+    @Test
+    public void rtlTest() throws Throwable {
+        if (mOneLine && mConfig.mOrientation != VERTICAL) {
+            return;// nothing to test
+        }
+        if (mConfig.mSpanCount == 1) {
+            mConfig.mSpanCount = 2;
+        }
+        String logPrefix = mConfig + ", changeRtlAfterLayout:" + mChangeRtlAfter + ","
+                + "oneLine:" + mOneLine + " itemsWrap:" + mItemsWrapContent;
+        mConfig.mItemCount = 5;
+        if (mOneLine) {
+            mConfig.mSpanCount = mConfig.mItemCount + 1;
+        } else {
+            mConfig.mSpanCount = Math.min(mConfig.mItemCount - 1, mConfig.mSpanCount);
+        }
+
+        RecyclerView rv = setupBasic(mConfig, new GridTestAdapter(mConfig.mItemCount) {
+            @Override
+            public void onBindViewHolder(TestViewHolder holder,
+                    int position) {
+                super.onBindViewHolder(holder, position);
+                if (mItemsWrapContent) {
+                    ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
+                    if (lp == null) {
+                        lp = mGlm.generateDefaultLayoutParams();
+                    }
+                    if (mConfig.mOrientation == HORIZONTAL) {
+                        lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+                    } else {
+                        lp.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+                    }
+                }
+            }
+        });
+        if (mChangeRtlAfter) {
+            waitForFirstLayout(rv);
+            mGlm.expectLayout(1);
+            mGlm.setFakeRtl(true);
+            mGlm.waitForLayout(2);
+        } else {
+            mGlm.mFakeRTL = true;
+            waitForFirstLayout(rv);
+        }
+
+        assertEquals("view should become rtl", true, mGlm.isLayoutRTL());
+        OrientationHelper helper = OrientationHelper.createHorizontalHelper(mGlm);
+        View child0 = mGlm.findViewByPosition(0);
+        final int secondChildPos = mConfig.mOrientation == VERTICAL ? 1
+                : mConfig.mSpanCount;
+        View child1 = mGlm.findViewByPosition(secondChildPos);
+        assertNotNull(logPrefix + " child position 0 should be laid out", child0);
+        assertNotNull(
+                logPrefix + " second child position " + (secondChildPos) + " should be laid out",
+                child1);
+        if (mConfig.mOrientation == VERTICAL || !mConfig.mReverseLayout) {
+            assertTrue(logPrefix + " second child should be to the left of first child",
+                    helper.getDecoratedStart(child0) >= helper.getDecoratedEnd(child1));
+            assertEquals(logPrefix + " first child should be right aligned",
+                    helper.getDecoratedEnd(child0), helper.getEndAfterPadding());
+        } else {
+            assertTrue(logPrefix + " first child should be to the left of second child",
+                    helper.getDecoratedStart(child1) >= helper.getDecoratedEnd(child0));
+            assertEquals(logPrefix + " first child should be left aligned",
+                    helper.getDecoratedStart(child0), helper.getStartAfterPadding());
+        }
+        checkForMainThreadException();
+    }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
index 2cd562f..2f97114 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
@@ -16,11 +16,10 @@
 
 package android.support.v7.widget;
 
-import org.junit.Before;
+import org.hamcrest.CoreMatchers;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import android.content.Context;
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
@@ -30,74 +29,29 @@
 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
 import android.test.UiThreadTest;
 import android.test.suitebuilder.annotation.MediumTest;
-import android.util.Log;
 import android.util.SparseIntArray;
 import android.util.StateSet;
-import android.view.FocusFinder;
 import android.view.View;
 import android.view.ViewGroup;
 
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.BitSet;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
 import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
-import static java.util.concurrent.TimeUnit.SECONDS;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
 
 @MediumTest
 @RunWith(AndroidJUnit4.class)
-public class GridLayoutManagerTest extends BaseRecyclerViewInstrumentationTest {
-
-    static final String TAG = "GridLayoutManagerTest";
-
-    static final boolean DEBUG = false;
-
-    WrappedGridLayoutManager mGlm;
-
-    GridTestAdapter mAdapter;
-
-    final List<Config> mBaseVariations = new ArrayList<Config>();
-
-    @Before
-    public void setUp() throws Exception {
-        for (int orientation : new int[]{VERTICAL, HORIZONTAL}) {
-            for (boolean reverseLayout : new boolean[]{false, true}) {
-                for (int spanCount : new int[]{1, 3, 4}) {
-                    mBaseVariations.add(new Config(spanCount, orientation, reverseLayout));
-                }
-            }
-        }
-    }
-
-    public RecyclerView setupBasic(Config config) throws Throwable {
-        return setupBasic(config, new GridTestAdapter(config.mItemCount));
-    }
-
-    public RecyclerView setupBasic(Config config, GridTestAdapter testAdapter) throws Throwable {
-        RecyclerView recyclerView = new RecyclerView(getActivity());
-        mAdapter = testAdapter;
-        mGlm = new WrappedGridLayoutManager(getActivity(), config.mSpanCount, config.mOrientation,
-                config.mReverseLayout);
-        mAdapter.assignSpanSizeLookup(mGlm);
-        recyclerView.setAdapter(mAdapter);
-        recyclerView.setLayoutManager(mGlm);
-        return recyclerView;
-    }
-
-    public void waitForFirstLayout(RecyclerView recyclerView) throws Throwable {
-        mGlm.expectLayout(1);
-        setRecyclerView(recyclerView);
-        mGlm.waitForLayout(2);
-    }
+public class GridLayoutManagerTest extends BaseGridLayoutManagerTest {
 
     @Test
     public void focusSearchFailureUp() throws Throwable {
@@ -109,36 +63,90 @@
         focusSearchFailure(true);
     }
 
-    public void focusSearchFailure(boolean scrollDown) throws Throwable {
+    @Test
+    public void scrollToBadOffset() throws Throwable {
+        scrollToBadOffset(false);
+    }
+
+    @Test
+    public void scrollToBadOffsetReverse() throws Throwable {
+        scrollToBadOffset(true);
+    }
+
+    private void scrollToBadOffset(boolean reverseLayout) throws Throwable {
+        final int w = 500;
+        final int h = 1000;
+        RecyclerView recyclerView = setupBasic(new Config(2, 100).reverseLayout(reverseLayout),
+                new GridTestAdapter(100) {
+                    @Override
+                    public void onBindViewHolder(TestViewHolder holder,
+                            int position) {
+                        super.onBindViewHolder(holder, position);
+                        ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
+                        if (lp == null) {
+                            lp = new ViewGroup.LayoutParams(w / 2, h / 2);
+                            holder.itemView.setLayoutParams(lp);
+                        } else {
+                            lp.width = w / 2;
+                            lp.height = h / 2;
+                            holder.itemView.setLayoutParams(lp);
+                        }
+                    }
+                });
+        TestedFrameLayout.FullControlLayoutParams lp
+                = new TestedFrameLayout.FullControlLayoutParams(w, h);
+        recyclerView.setLayoutParams(lp);
+        waitForFirstLayout(recyclerView);
+        mGlm.expectLayout(1);
+        scrollToPosition(11);
+        mGlm.waitForLayout(2);
+        // assert spans and position etc
+        for (int i = 0; i < mGlm.getChildCount(); i++) {
+            View child = mGlm.getChildAt(i);
+            GridLayoutManager.LayoutParams params = (GridLayoutManager.LayoutParams) child
+                    .getLayoutParams();
+            assertThat("span index for child at " + i + " with position " + params
+                            .getViewAdapterPosition(),
+                    params.getSpanIndex(), CoreMatchers.is(params.getViewAdapterPosition() % 2));
+        }
+        // assert spans and positions etc.
+        int lastVisible = mGlm.findLastVisibleItemPosition();
+        // this should be the scrolled child
+        assertThat(lastVisible, CoreMatchers.is(11));
+    }
+
+    private void focusSearchFailure(boolean scrollDown) throws Throwable {
         final RecyclerView recyclerView = setupBasic(new Config(3, 31).reverseLayout(!scrollDown)
                 , new GridTestAdapter(31, 1) {
-            RecyclerView mAttachedRv;
-            @Override
-            public TestViewHolder onCreateViewHolder(ViewGroup parent,
-                    int viewType) {
-                TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
-                testViewHolder.itemView.setFocusable(true);
-                testViewHolder.itemView.setFocusableInTouchMode(true);
-                // Good to have colors for debugging
-                StateListDrawable stl = new StateListDrawable();
-                stl.addState(new int[]{android.R.attr.state_focused}, new ColorDrawable(Color.RED));
-                stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE));
-                testViewHolder.itemView.setBackground(stl);
-                return testViewHolder;
-            }
+                    RecyclerView mAttachedRv;
 
-            @Override
-            public void onAttachedToRecyclerView(RecyclerView recyclerView) {
-                mAttachedRv = recyclerView;
-            }
+                    @Override
+                    public TestViewHolder onCreateViewHolder(ViewGroup parent,
+                            int viewType) {
+                        TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
+                        testViewHolder.itemView.setFocusable(true);
+                        testViewHolder.itemView.setFocusableInTouchMode(true);
+                        // Good to have colors for debugging
+                        StateListDrawable stl = new StateListDrawable();
+                        stl.addState(new int[]{android.R.attr.state_focused},
+                                new ColorDrawable(Color.RED));
+                        stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE));
+                        testViewHolder.itemView.setBackground(stl);
+                        return testViewHolder;
+                    }
 
-            @Override
-            public void onBindViewHolder(TestViewHolder holder,
-                    int position) {
-                super.onBindViewHolder(holder, position);
-                holder.itemView.setMinimumHeight(mAttachedRv.getHeight() / 3);
-            }
-        });
+                    @Override
+                    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
+                        mAttachedRv = recyclerView;
+                    }
+
+                    @Override
+                    public void onBindViewHolder(TestViewHolder holder,
+                            int position) {
+                        super.onBindViewHolder(holder, position);
+                        holder.itemView.setMinimumHeight(mAttachedRv.getHeight() / 3);
+                    }
+                });
         waitForFirstLayout(recyclerView);
 
         View viewToFocus = recyclerView.findViewHolderForAdapterPosition(1).itemView;
@@ -305,7 +313,8 @@
         }
         waitForFirstLayout(rv);
 
-        assertTrue("[test sanity] some views should be laid out", mRecyclerView.getChildCount() > 0);
+        assertTrue("[test sanity] some views should be laid out",
+                mRecyclerView.getChildCount() > 0);
         for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
             View child = mRecyclerView.getChildAt(i);
             final int size = config.mOrientation == HORIZONTAL ? child.getWidth()
@@ -317,68 +326,6 @@
     }
 
     @Test
-    public void rTL() throws Throwable {
-        for (boolean changeRtlAfter : new boolean[]{false, true}) {
-            for (boolean oneLine : new boolean[]{false, true}) {
-                for (Config config : mBaseVariations) {
-                    rtlTest(config, changeRtlAfter, oneLine);
-                    removeRecyclerView();
-                }
-            }
-        }
-    }
-
-    void rtlTest(Config config, boolean changeRtlAfter, boolean oneLine) throws Throwable {
-        if (oneLine && config.mOrientation != VERTICAL) {
-            return;// nothing to test
-        }
-        if (config.mSpanCount == 1) {
-            config.mSpanCount = 2;
-        }
-        String logPrefix = config + ", changeRtlAfterLayout:" + changeRtlAfter + ", oneLine:" + oneLine;
-        config.mItemCount = 5;
-        if (oneLine) {
-            config.mSpanCount = config.mItemCount + 1;
-        } else {
-            config.mSpanCount = Math.min(config.mItemCount - 1, config.mSpanCount);
-        }
-
-        RecyclerView rv = setupBasic(config);
-        if (changeRtlAfter) {
-            waitForFirstLayout(rv);
-            mGlm.expectLayout(1);
-            mGlm.setFakeRtl(true);
-            mGlm.waitForLayout(2);
-        } else {
-            mGlm.mFakeRTL = true;
-            waitForFirstLayout(rv);
-        }
-
-        assertEquals("view should become rtl", true, mGlm.isLayoutRTL());
-        OrientationHelper helper = OrientationHelper.createHorizontalHelper(mGlm);
-        View child0 = mGlm.findViewByPosition(0);
-        final int secondChildPos = config.mOrientation == VERTICAL ? 1
-                : config.mSpanCount;
-        View child1 = mGlm.findViewByPosition(secondChildPos);
-        assertNotNull(logPrefix + " child position 0 should be laid out", child0);
-        assertNotNull(
-                logPrefix + " second child position " + (secondChildPos) + " should be laid out",
-                child1);
-        if (config.mOrientation == VERTICAL || !config.mReverseLayout) {
-            assertTrue(logPrefix + " second child should be to the left of first child",
-                    helper.getDecoratedStart(child0) >= helper.getDecoratedEnd(child1));
-            assertEquals(logPrefix + " first child should be right aligned",
-                    helper.getDecoratedEnd(child0), helper.getEndAfterPadding());
-        } else {
-            assertTrue(logPrefix + " first child should be to the left of second child",
-                    helper.getDecoratedStart(child1) >= helper.getDecoratedEnd(child0));
-            assertEquals(logPrefix + " first child should be left aligned",
-                    helper.getDecoratedStart(child0), helper.getStartAfterPadding());
-        }
-        checkForMainThreadException();
-    }
-
-    @Test
     public void movingAGroupOffScreenForAddedItems() throws Throwable {
         final RecyclerView rv = setupBasic(new Config(3, 100));
         final int[] maxId = new int[1];
@@ -396,7 +343,7 @@
                 return 3;
             }
         });
-        ((SimpleItemAnimator)rv.getItemAnimator()).setSupportsChangeAnimations(true);
+        ((SimpleItemAnimator) rv.getItemAnimator()).setSupportsChangeAnimations(true);
         waitForFirstLayout(rv);
         View lastView = rv.getChildAt(rv.getChildCount() - 1);
         final int lastPos = rv.getChildAdapterPosition(lastView);
@@ -414,50 +361,6 @@
     }
 
     @Test
-    public void cachedBorders() throws Throwable {
-        List<Config> testConfigurations = new ArrayList<Config>(mBaseVariations);
-        testConfigurations.addAll(cachedBordersTestConfigs());
-        for (Config config : testConfigurations) {
-            gridCachedBorderstTest(config);
-        }
-    }
-
-    private void gridCachedBorderstTest(Config config) throws Throwable {
-        RecyclerView recyclerView = setupBasic(config);
-        waitForFirstLayout(recyclerView);
-        final boolean vertical = config.mOrientation == GridLayoutManager.VERTICAL;
-        final int expectedSizeSum = vertical ? recyclerView.getWidth() : recyclerView.getHeight();
-        final int lastVisible = mGlm.findLastVisibleItemPosition();
-        for (int i = 0; i < lastVisible; i += config.mSpanCount) {
-            if ((i+1)*config.mSpanCount - 1 < lastVisible) {
-                int childrenSizeSum = 0;
-                for (int j = 0; j < config.mSpanCount; j++) {
-                    View child = recyclerView.getChildAt(i * config.mSpanCount + j);
-                    childrenSizeSum += vertical ? child.getWidth() : child.getHeight();
-                }
-                assertEquals(expectedSizeSum, childrenSizeSum);
-            }
-        }
-        removeRecyclerView();
-    }
-
-    private List<Config> cachedBordersTestConfigs() {
-        ArrayList<Config> configs = new ArrayList<Config>();
-        final int [] spanCounts = new int[]{88, 279, 741};
-        final int [] spanPerItem = new int[]{11, 9, 13};
-        for (int orientation : new int[]{VERTICAL, HORIZONTAL}) {
-            for (boolean reverseLayout : new boolean[]{false, true}) {
-                for (int i = 0 ; i < spanCounts.length; i++) {
-                    Config config = new Config(spanCounts[i], orientation, reverseLayout);
-                    config.mSpanPerItem = spanPerItem[i];
-                    configs.add(config);
-                }
-            }
-        }
-        return configs;
-    }
-
-    @Test
     public void layoutParams() throws Throwable {
         layoutParamsTest(GridLayoutManager.HORIZONTAL);
         removeRecyclerView();
@@ -497,10 +400,10 @@
                 orientation == HORIZONTAL ? itemInfo.getColumnIndex() : itemInfo.getRowIndex());
         assertEquals("result should have span index",
                 ssl.getSpanIndex(position, mGlm.getSpanCount()),
-                orientation == HORIZONTAL ? itemInfo.getRowIndex() :  itemInfo.getColumnIndex());
+                orientation == HORIZONTAL ? itemInfo.getRowIndex() : itemInfo.getColumnIndex());
         assertEquals("result should have span size",
                 ssl.getSpanSize(position),
-                orientation == HORIZONTAL ? itemInfo.getRowSpan() :  itemInfo.getColumnSpan());
+                orientation == HORIZONTAL ? itemInfo.getRowSpan() : itemInfo.getColumnSpan());
     }
 
     public GridLayoutManager.LayoutParams ensureGridLp(View view) {
@@ -578,13 +481,6 @@
         assertEquals(secondRowSize, getSize(mGlm.findViewByPosition(5)));
     }
 
-    private int getSize(View view) {
-        if (mGlm.getOrientation() == GridLayoutManager.HORIZONTAL) {
-            return view.getWidth();
-        }
-        return view.getHeight();
-    }
-
     @Test
     public void anchorUpdate() throws InterruptedException {
         GridLayoutManager glm = new GridLayoutManager(getActivity(), 11);
@@ -606,20 +502,49 @@
         RecyclerView.State state = new RecyclerView.State();
         mRecyclerView = new RecyclerView(getActivity());
         state.mItemCount = 1000;
-        glm.onAnchorReady(mRecyclerView.mRecycler, state, glm.mAnchorInfo);
+        glm.onAnchorReady(mRecyclerView.mRecycler, state, glm.mAnchorInfo,
+                LinearLayoutManager.LayoutState.ITEM_DIRECTION_TAIL);
         assertEquals("gm should keep anchor in first span", 11, glm.mAnchorInfo.mPosition);
 
+        glm.onAnchorReady(mRecyclerView.mRecycler, state, glm.mAnchorInfo,
+                LinearLayoutManager.LayoutState.ITEM_DIRECTION_HEAD);
+        assertEquals("gm should keep anchor in last span in the row", 20,
+                glm.mAnchorInfo.mPosition);
+
+        glm.mAnchorInfo.mPosition = 5;
+        glm.onAnchorReady(mRecyclerView.mRecycler, state, glm.mAnchorInfo,
+                LinearLayoutManager.LayoutState.ITEM_DIRECTION_HEAD);
+        assertEquals("gm should keep anchor in last span in the row", 10,
+                glm.mAnchorInfo.mPosition);
+
         glm.mAnchorInfo.mPosition = 13;
-        glm.onAnchorReady(mRecyclerView.mRecycler, state, glm.mAnchorInfo);
+        glm.onAnchorReady(mRecyclerView.mRecycler, state, glm.mAnchorInfo,
+                LinearLayoutManager.LayoutState.ITEM_DIRECTION_TAIL);
         assertEquals("gm should move anchor to first span", 11, glm.mAnchorInfo.mPosition);
 
+        glm.onAnchorReady(mRecyclerView.mRecycler, state, glm.mAnchorInfo,
+                LinearLayoutManager.LayoutState.ITEM_DIRECTION_HEAD);
+        assertEquals("gm should keep anchor in last span in the row", 20,
+                glm.mAnchorInfo.mPosition);
+
         glm.mAnchorInfo.mPosition = 23;
-        glm.onAnchorReady(mRecyclerView.mRecycler, state, glm.mAnchorInfo);
+        glm.onAnchorReady(mRecyclerView.mRecycler, state, glm.mAnchorInfo,
+                LinearLayoutManager.LayoutState.ITEM_DIRECTION_TAIL);
         assertEquals("gm should move anchor to first span", 21, glm.mAnchorInfo.mPosition);
 
+        glm.onAnchorReady(mRecyclerView.mRecycler, state, glm.mAnchorInfo,
+                LinearLayoutManager.LayoutState.ITEM_DIRECTION_HEAD);
+        assertEquals("gm should keep anchor in last span in the row", 25,
+                glm.mAnchorInfo.mPosition);
+
         glm.mAnchorInfo.mPosition = 35;
-        glm.onAnchorReady(mRecyclerView.mRecycler, state, glm.mAnchorInfo);
+        glm.onAnchorReady(mRecyclerView.mRecycler, state, glm.mAnchorInfo,
+                LinearLayoutManager.LayoutState.ITEM_DIRECTION_TAIL);
         assertEquals("gm should move anchor to first span", 31, glm.mAnchorInfo.mPosition);
+        glm.onAnchorReady(mRecyclerView.mRecycler, state, glm.mAnchorInfo,
+                LinearLayoutManager.LayoutState.ITEM_DIRECTION_HEAD);
+        assertEquals("gm should keep anchor in last span in the row", 35,
+                glm.mAnchorInfo.mPosition);
     }
 
     @Test
@@ -922,15 +847,6 @@
     }
 
     @Test
-    public void scrollBackAndPreservePositions() throws Throwable {
-        for (Config config : mBaseVariations) {
-            config.mItemCount = 150;
-            scrollBackAndPreservePositionsTest(config);
-            removeRecyclerView();
-        }
-    }
-
-    @Test
     public void spanSizeChange() throws Throwable {
         final RecyclerView rv = setupBasic(new Config(3, 100));
         waitForFirstLayout(rv);
@@ -967,277 +883,4 @@
         assertEquals("item index 5 should be in span 2", 0,
                 getLp(mGlm.findViewByPosition(5)).getSpanIndex());
     }
-
-    GridLayoutManager.LayoutParams getLp(View view) {
-        return (GridLayoutManager.LayoutParams) view.getLayoutParams();
-    }
-
-    public void scrollBackAndPreservePositionsTest(final Config config) throws Throwable {
-        final RecyclerView rv = setupBasic(config);
-        for (int i = 1; i < mAdapter.getItemCount(); i += config.mSpanCount + 2) {
-            mAdapter.setFullSpan(i);
-        }
-        waitForFirstLayout(rv);
-        final int[] globalPositions = new int[mAdapter.getItemCount()];
-        Arrays.fill(globalPositions, Integer.MIN_VALUE);
-        final int scrollStep = (mGlm.mOrientationHelper.getTotalSpace() / 20)
-                * (config.mReverseLayout ? -1 : 1);
-        final String logPrefix = config.toString();
-        final int[] globalPos = new int[1];
-        runTestOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                assertSame("test sanity", mRecyclerView, rv);
-                int globalScrollPosition = 0;
-                int visited = 0;
-                while (visited < mAdapter.getItemCount()) {
-                    for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
-                        View child = mRecyclerView.getChildAt(i);
-                        final int pos = mRecyclerView.getChildLayoutPosition(child);
-                        if (globalPositions[pos] != Integer.MIN_VALUE) {
-                            continue;
-                        }
-                        visited++;
-                        GridLayoutManager.LayoutParams lp = (GridLayoutManager.LayoutParams)
-                                child.getLayoutParams();
-                        if (config.mReverseLayout) {
-                            globalPositions[pos] = globalScrollPosition +
-                                    mGlm.mOrientationHelper.getDecoratedEnd(child);
-                        } else {
-                            globalPositions[pos] = globalScrollPosition +
-                                    mGlm.mOrientationHelper.getDecoratedStart(child);
-                        }
-                        assertEquals(logPrefix + " span index should match",
-                                mGlm.getSpanSizeLookup().getSpanIndex(pos, mGlm.getSpanCount()),
-                                lp.getSpanIndex());
-                    }
-                    int scrolled = mGlm.scrollBy(scrollStep,
-                            mRecyclerView.mRecycler, mRecyclerView.mState);
-                    globalScrollPosition += scrolled;
-                    if (scrolled == 0) {
-                        assertEquals(
-                                logPrefix + " If scroll is complete, all views should be visited",
-                                visited, mAdapter.getItemCount());
-                    }
-                }
-                if (DEBUG) {
-                    Log.d(TAG, "done recording positions " + Arrays.toString(globalPositions));
-                }
-                globalPos[0] = globalScrollPosition;
-            }
-        });
-        checkForMainThreadException();
-        // test sanity, ensure scroll happened
-        runTestOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                final int childCount = mGlm.getChildCount();
-                final BitSet expectedPositions = new BitSet();
-                for (int i = 0; i < childCount; i ++) {
-                    expectedPositions.set(mAdapter.getItemCount() - i - 1);
-                }
-                for (int i = 0; i <childCount; i ++) {
-                    final View view = mGlm.getChildAt(i);
-                    int position = mGlm.getPosition(view);
-                    assertTrue("child position should be in last page", expectedPositions.get(position));
-                }
-            }
-        });
-        getInstrumentation().waitForIdleSync();
-        runTestOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                int globalScrollPosition = globalPos[0];
-                // now scroll back and make sure global positions match
-                BitSet shouldTest = new BitSet(mAdapter.getItemCount());
-                shouldTest.set(0, mAdapter.getItemCount() - 1, true);
-                String assertPrefix = config
-                        + " global pos must match when scrolling in reverse for position ";
-                int scrollAmount = Integer.MAX_VALUE;
-                while (!shouldTest.isEmpty() && scrollAmount != 0) {
-                    for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
-                        View child = mRecyclerView.getChildAt(i);
-                        int pos = mRecyclerView.getChildLayoutPosition(child);
-                        if (!shouldTest.get(pos)) {
-                            continue;
-                        }
-                        GridLayoutManager.LayoutParams lp = (GridLayoutManager.LayoutParams)
-                                child.getLayoutParams();
-                        shouldTest.clear(pos);
-                        int globalPos;
-                        if (config.mReverseLayout) {
-                            globalPos = globalScrollPosition +
-                                    mGlm.mOrientationHelper.getDecoratedEnd(child);
-                        } else {
-                            globalPos = globalScrollPosition +
-                                    mGlm.mOrientationHelper.getDecoratedStart(child);
-                        }
-                        assertEquals(assertPrefix + pos,
-                                globalPositions[pos], globalPos);
-                        assertEquals("span index should match",
-                                mGlm.getSpanSizeLookup().getSpanIndex(pos, mGlm.getSpanCount()),
-                                lp.getSpanIndex());
-                    }
-                    scrollAmount = mGlm.scrollBy(-scrollStep,
-                            mRecyclerView.mRecycler, mRecyclerView.mState);
-                    globalScrollPosition += scrollAmount;
-                }
-                assertTrue("all views should be seen", shouldTest.isEmpty());
-            }
-        });
-        checkForMainThreadException();
-    }
-
-    class WrappedGridLayoutManager extends GridLayoutManager {
-
-        CountDownLatch mLayoutLatch;
-
-        List<Callback> mCallbacks = new ArrayList<Callback>();
-
-        Boolean mFakeRTL;
-
-        public WrappedGridLayoutManager(Context context, int spanCount) {
-            super(context, spanCount);
-        }
-
-        public WrappedGridLayoutManager(Context context, int spanCount, int orientation,
-                boolean reverseLayout) {
-            super(context, spanCount, orientation, reverseLayout);
-        }
-
-        @Override
-        protected boolean isLayoutRTL() {
-            return mFakeRTL == null ? super.isLayoutRTL() : mFakeRTL;
-        }
-
-        public void setFakeRtl(Boolean fakeRtl) {
-            mFakeRTL = fakeRtl;
-            try {
-                requestLayoutOnUIThread(mRecyclerView);
-            } catch (Throwable throwable) {
-                postExceptionToInstrumentation(throwable);
-            }
-        }
-
-        @Override
-        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
-            try {
-                for (Callback callback : mCallbacks) {
-                    callback.onBeforeLayout(recycler, state);
-                }
-                super.onLayoutChildren(recycler, state);
-                for (Callback callback : mCallbacks) {
-                    callback.onAfterLayout(recycler, state);
-                }
-            } catch (Throwable t) {
-                postExceptionToInstrumentation(t);
-            }
-            mLayoutLatch.countDown();
-        }
-
-        @Override
-        LayoutState createLayoutState() {
-            return new LayoutState() {
-                @Override
-                View next(RecyclerView.Recycler recycler) {
-                    final boolean hadMore = hasMore(mRecyclerView.mState);
-                    final int position = mCurrentPosition;
-                    View next = super.next(recycler);
-                    assertEquals("if has more, should return a view", hadMore, next != null);
-                    assertEquals("position of the returned view must match current position",
-                            position, RecyclerView.getChildViewHolderInt(next).getLayoutPosition());
-                    return next;
-                }
-            };
-        }
-
-        public void expectLayout(int layoutCount) {
-            mLayoutLatch = new CountDownLatch(layoutCount);
-        }
-
-        public void waitForLayout(int seconds) throws InterruptedException {
-            mLayoutLatch.await(seconds, SECONDS);
-        }
-    }
-
-    class Config {
-
-        int mSpanCount;
-        int mOrientation = GridLayoutManager.VERTICAL;
-        int mItemCount = 1000;
-        int mSpanPerItem = 1;
-        boolean mReverseLayout = false;
-
-        Config(int spanCount, int itemCount) {
-            mSpanCount = spanCount;
-            mItemCount = itemCount;
-        }
-
-        public Config(int spanCount, int orientation, boolean reverseLayout) {
-            mSpanCount = spanCount;
-            mOrientation = orientation;
-            mReverseLayout = reverseLayout;
-        }
-
-        Config orientation(int orientation) {
-            mOrientation = orientation;
-            return this;
-        }
-
-        @Override
-        public String toString() {
-            return "Config{" +
-                    "mSpanCount=" + mSpanCount +
-                    ", mOrientation=" + (mOrientation == GridLayoutManager.HORIZONTAL ? "h" : "v") +
-                    ", mItemCount=" + mItemCount +
-                    ", mReverseLayout=" + mReverseLayout +
-                    '}';
-        }
-
-        public Config reverseLayout(boolean reverseLayout) {
-            mReverseLayout = reverseLayout;
-            return this;
-        }
-
-
-    }
-
-    class GridTestAdapter extends TestAdapter {
-
-        Set<Integer> mFullSpanItems = new HashSet<Integer>();
-        int mSpanPerItem = 1;
-
-        GridTestAdapter(int count) {
-            super(count);
-        }
-
-        GridTestAdapter(int count, int spanPerItem) {
-            super(count);
-            mSpanPerItem = spanPerItem;
-        }
-
-        void setFullSpan(int... items) {
-            for (int i : items) {
-                mFullSpanItems.add(i);
-            }
-        }
-
-        void assignSpanSizeLookup(final GridLayoutManager glm) {
-            glm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
-                @Override
-                public int getSpanSize(int position) {
-                    return mFullSpanItems.contains(position) ? glm.getSpanCount() : mSpanPerItem;
-                }
-            });
-        }
-    }
-
-    class Callback {
-
-        public void onBeforeLayout(RecyclerView.Recycler recycler, RecyclerView.State state) {
-        }
-
-        public void onAfterLayout(RecyclerView.Recycler recycler, RecyclerView.State state) {
-        }
-    }
 }
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerWrapContentTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerWrapContentTest.java
new file mode 100644
index 0000000..c8fdbeb
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerWrapContentTest.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.v7.widget;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.view.Gravity;
+
+import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.support.v7.widget.BaseWrapContentWithAspectRatioTest.*;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+@RunWith(JUnit4.class)
+public class GridLayoutManagerWrapContentTest extends BaseWrapContentTest {
+
+    public GridLayoutManagerWrapContentTest() {
+        super(new WrapContentConfig(false, false));
+    }
+
+    @Override
+    RecyclerView.LayoutManager createLayoutManager() {
+        return new GridLayoutManager(getActivity(), 3);
+    }
+
+    @Test
+    public void testHandleSecondLineChangingBorders() throws Throwable {
+        TestedFrameLayout.FullControlLayoutParams lp =
+                mWrapContentConfig.toLayoutParams(WRAP_CONTENT, WRAP_CONTENT);
+        WrapContentAdapter adapter = new WrapContentAdapter(
+                new MeasureBehavior(10, 10, WRAP_CONTENT, WRAP_CONTENT),
+                new MeasureBehavior(10, 10, WRAP_CONTENT, WRAP_CONTENT),
+                new MeasureBehavior(10, 10, WRAP_CONTENT, WRAP_CONTENT),
+                new MeasureBehavior(20, 10, WRAP_CONTENT, WRAP_CONTENT)
+        );
+        Rect[] expected = new Rect[] {
+                new Rect(0, 0, 10, 10),
+                new Rect(20, 0, 30, 10),
+                new Rect(40, 0, 50, 10),
+                new Rect(0, 10, 20, 20)
+        };
+        layoutAndCheck(lp, adapter, expected, 60, 20);
+    }
+
+    @Test
+    public void testSecondLineAffectingBordersWithAspectRatio() throws Throwable {
+        TestedFrameLayout.FullControlLayoutParams lp =
+                mWrapContentConfig.toLayoutParams(WRAP_CONTENT, WRAP_CONTENT);
+        WrapContentAdapter adapter = new WrapContentAdapter(
+                new AspectRatioMeasureBehavior(10, 5, MATCH_PARENT, WRAP_CONTENT)
+                        .aspectRatio(HORIZONTAL, .5f),
+                new MeasureBehavior(10, 5, WRAP_CONTENT, WRAP_CONTENT),
+                new MeasureBehavior(10, 5, MATCH_PARENT, WRAP_CONTENT),
+                new MeasureBehavior(20, 10, WRAP_CONTENT, WRAP_CONTENT)
+        );
+        Rect[] expected = new Rect[] {
+                new Rect(0, 0, 20, 10),
+                new Rect(20, 0, 30, 10),
+                new Rect(40, 0, 60, 10),
+                new Rect(0, 10, 20, 20)
+        };
+        layoutAndCheck(lp, adapter, expected, 60, 20);
+    }
+
+    @Override
+    protected int getVerticalGravity(RecyclerView.LayoutManager layoutManager) {
+        return Gravity.TOP;
+    }
+
+    @Override
+    protected int getHorizontalGravity(RecyclerView.LayoutManager layoutManager) {
+        return Gravity.LEFT;
+    }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerWrapContentWithAspectRatioTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerWrapContentWithAspectRatioTest.java
new file mode 100644
index 0000000..d33611c
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerWrapContentWithAspectRatioTest.java
@@ -0,0 +1,354 @@
+/*
+ * 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 org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import android.graphics.Color;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
+import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static android.support.v7.widget.BaseWrapContentTest.WrapContentConfig;
+import static android.support.v7.widget.GridLayoutManagerTest.Config;
+import static org.hamcrest.CoreMatchers.*;
+import static android.view.View.MeasureSpec.UNSPECIFIED;
+import static android.view.View.MeasureSpec.AT_MOST;
+import static android.view.View.MeasureSpec.EXACTLY;
+
+@RunWith(Parameterized.class)
+@MediumTest
+public class GridLayoutManagerWrapContentWithAspectRatioTest
+        extends BaseWrapContentWithAspectRatioTest {
+
+    @Parameterized.Parameters(name = "{0} {1} {2}")
+    public static List<Object[]> params() {
+        List<Object[]> params = new ArrayList<>();
+        for (int orientation : new int[]{VERTICAL, HORIZONTAL}) {
+            for (boolean reverseLayout : new boolean[]{false, true}) {
+                for (boolean unlimitedW : new boolean[]{true, false}) {
+                    for (boolean unlimitedH : new boolean[]{true, false}) {
+                        for (int behavior1Size : new int[]{8, 10, 12}) {
+                            params.add(new Object[]{
+                                    new WrapContentConfig(unlimitedW, unlimitedH),
+                                    new Config(3, orientation, reverseLayout),
+                                    behavior1Size
+                            });
+                        }
+
+                    }
+                }
+
+            }
+        }
+        return params;
+    }
+
+    private GridLayoutManagerTest.Config mConfig;
+
+    private int mBehavior1Size;
+
+    int mTestOrientation;
+
+    boolean mUnlimited;
+
+    RecyclerView.LayoutManager mLayoutManager;
+
+    BaseWrapContentTest.WrappedRecyclerView mRecyclerView;
+
+    OrientationHelper mHelper;
+
+    public GridLayoutManagerWrapContentWithAspectRatioTest(WrapContentConfig wrapContentConfig,
+            GridLayoutManagerTest.Config config, int behavior1Size) {
+        super(wrapContentConfig);
+        mConfig = config;
+        mBehavior1Size = behavior1Size;
+    }
+
+    @Before
+    public final void init() {
+        TestedFrameLayout.FullControlLayoutParams lp =
+                mWrapContentConfig.toLayoutParams(WRAP_CONTENT, WRAP_CONTENT);
+        if (mConfig.mOrientation == VERTICAL) {
+            mTestOrientation = HORIZONTAL;
+            mUnlimited = lp.wSpec != null;
+        } else {
+            mTestOrientation = VERTICAL;
+            mUnlimited = lp.hSpec != null;
+        }
+        mLayoutManager = createFromConfig();
+
+        mRecyclerView = new BaseWrapContentTest.WrappedRecyclerView(getActivity());
+        mHelper = OrientationHelper.createOrientationHelper(
+                mLayoutManager, 1 - mConfig.mOrientation);
+
+        mRecyclerView.setBackgroundColor(Color.rgb(0, 0, 255));
+        mRecyclerView.setLayoutManager(mLayoutManager);
+        mRecyclerView.setLayoutParams(lp);
+    }
+
+    @Test
+    public void testChildWithMultipleSpans() throws Throwable {
+        MeasureBehavior behavior1;
+        behavior1 = new MeasureBehavior(mBehavior1Size, mBehavior1Size,
+                mTestOrientation == HORIZONTAL ? MATCH_PARENT : WRAP_CONTENT,
+                mTestOrientation == VERTICAL ? MATCH_PARENT : WRAP_CONTENT);
+
+        MeasureBehavior behavior2 = new MeasureBehavior(
+                mTestOrientation == HORIZONTAL ? 30 : 10,
+                mTestOrientation == VERTICAL ? 30 : 10, WRAP_CONTENT, WRAP_CONTENT);
+        WrapContentAdapter adapter = new WrapContentAdapter(behavior1, behavior2);
+        ((GridLayoutManager) mLayoutManager).setSpanSizeLookup(
+                new GridLayoutManager.SpanSizeLookup() {
+                    @Override
+                    public int getSpanSize(int position) {
+                        return position == 1 ? 2 : 1;
+                    }
+                });
+        mRecyclerView.setAdapter(adapter);
+        setRecyclerView(mRecyclerView);
+        mRecyclerView.waitUntilLayout();
+
+        final int parentSize = getSize((View) mRecyclerView.getParent(), mTestOrientation);
+
+        if (mUnlimited) {
+            assertThat(behavior1.getSpec(0, mTestOrientation),
+                    MeasureSpecMatcher.mode(UNSPECIFIED));
+            assertThat(behavior2.getSpec(0, mTestOrientation),
+                    MeasureSpecMatcher.mode(UNSPECIFIED));
+        } else {
+            int[] borders = GridLayoutManager.calculateItemBorders(null,
+                    mConfig.mSpanCount, parentSize);
+            assertThat(behavior1.getSpec(0, mTestOrientation),
+                    MeasureSpecMatcher.is(borders[1] - borders[0], AT_MOST));
+            assertThat(behavior2.getSpec(0, mTestOrientation),
+                    MeasureSpecMatcher.is(borders[3] - borders[1], AT_MOST));
+        }
+        // child0 should be measured again because it measured its size as 10
+        assertThat(behavior1.getSpec(1, mTestOrientation),
+                MeasureSpecMatcher.is(15, EXACTLY));
+        assertThat(behavior1.getSpec(1, mConfig.mOrientation),
+                MeasureSpecMatcher.mode(UNSPECIFIED));
+        switch (mBehavior1Size) {
+            case 10:
+                assertThat(behavior1.measureSpecs.size(), is(2));
+                assertThat(behavior2.measureSpecs.size(), is(1));
+                break;
+            case 8:
+                assertThat(behavior2.measureSpecs.size(), is(1));
+                assertThat(behavior1.measureSpecs.size(), is(3));
+                assertThat(behavior1.getSpec(2, mTestOrientation),
+                        MeasureSpecMatcher.is(15, EXACTLY));
+                assertThat(behavior1.getSpec(2, mConfig.mOrientation),
+                        MeasureSpecMatcher.is(10, EXACTLY));
+                break;
+            case 12:
+                assertThat(behavior1.measureSpecs.size(), is(2));
+                assertThat(behavior2.measureSpecs.size(), is(2));
+                assertThat(behavior2.getSpec(1, mTestOrientation),
+                        MeasureSpecMatcher.is(30, AT_MOST));
+                assertThat(behavior2.getSpec(1, mConfig.mOrientation),
+                        MeasureSpecMatcher.is(12, EXACTLY));
+                break;
+        }
+
+        View child0 = mRecyclerView.getChildAt(0);
+        assertThat(getSize(child0, mTestOrientation), is(15));
+
+        View child1 = mRecyclerView.getChildAt(1);
+        assertThat(getSize(child1, mTestOrientation), is(30));
+
+        assertThat(mHelper.getDecoratedStart(child0), is(0));
+        assertThat(mHelper.getDecoratedStart(child1), is(15));
+
+        assertThat(mHelper.getDecoratedEnd(child0), is(15));
+        assertThat(mHelper.getDecoratedEnd(child1), is(45));
+
+        assertThat(mHelper.getDecoratedMeasurementInOther(child0),
+                is(Math.max(10, mBehavior1Size)));
+        assertThat(mHelper.getDecoratedMeasurementInOther(child1),
+                is(Math.max(10, mBehavior1Size)));
+
+        assertThat(getSize(mRecyclerView, mTestOrientation), is(45));
+        assertThat(getSize(mRecyclerView, 1 - mTestOrientation), is(Math.max(10, mBehavior1Size)));
+    }
+
+    @Test
+    public void testChildWithMatchParentInOtherDirection() throws Throwable {
+        MeasureBehavior behavior1;
+        behavior1 = new MeasureBehavior(mBehavior1Size, mBehavior1Size,
+                mTestOrientation == HORIZONTAL ? MATCH_PARENT : WRAP_CONTENT,
+                mTestOrientation == VERTICAL ? MATCH_PARENT : WRAP_CONTENT);
+
+        MeasureBehavior behavior2 = new MeasureBehavior(
+                mTestOrientation == HORIZONTAL ? 15 : 10,
+                mTestOrientation == VERTICAL ? 15 : 10, WRAP_CONTENT, WRAP_CONTENT);
+        WrapContentAdapter adapter = new WrapContentAdapter(behavior1, behavior2);
+
+        mRecyclerView.setAdapter(adapter);
+        setRecyclerView(mRecyclerView);
+        mRecyclerView.waitUntilLayout();
+        final int parentSize = getSize((View) mRecyclerView.getParent(), mTestOrientation);
+        if (mUnlimited) {
+            assertThat(behavior1.getSpec(0, mTestOrientation),
+                    MeasureSpecMatcher.mode(UNSPECIFIED));
+            assertThat(behavior2.getSpec(0, mTestOrientation),
+                    MeasureSpecMatcher.mode(UNSPECIFIED));
+        } else {
+            int[] borders = GridLayoutManager.calculateItemBorders(null, mConfig.mSpanCount,
+                    parentSize);
+            assertThat(behavior1.getSpec(0, mTestOrientation),
+                    MeasureSpecMatcher.is(borders[1] - borders[0], AT_MOST));
+            assertThat(behavior2.getSpec(0, mTestOrientation),
+                    MeasureSpecMatcher.is(borders[2] - borders[1], AT_MOST));
+        }
+        // child0 should be measured again because it measured its size as 10
+        assertThat(behavior1.getSpec(1, mTestOrientation),
+                MeasureSpecMatcher.is(15, EXACTLY));
+        assertThat(behavior1.getSpec(1, mConfig.mOrientation),
+                MeasureSpecMatcher.mode(UNSPECIFIED));
+        switch (mBehavior1Size) {
+            case 10:
+                assertThat(behavior1.measureSpecs.size(), is(2));
+                assertThat(behavior2.measureSpecs.size(), is(1));
+                break;
+            case 8:
+                assertThat(behavior2.measureSpecs.size(), is(1));
+                assertThat(behavior1.measureSpecs.size(), is(3));
+                assertThat(behavior1.getSpec(2, mTestOrientation),
+                        MeasureSpecMatcher.is(15, EXACTLY));
+                assertThat(behavior1.getSpec(2, mConfig.mOrientation),
+                        MeasureSpecMatcher.is(10, EXACTLY));
+                break;
+            case 12:
+                assertThat(behavior1.measureSpecs.size(), is(2));
+                assertThat(behavior2.measureSpecs.size(), is(2));
+                assertThat(behavior2.getSpec(1, mTestOrientation),
+                        MeasureSpecMatcher.is(15, AT_MOST));
+                assertThat(behavior2.getSpec(1, mConfig.mOrientation),
+                        MeasureSpecMatcher.is(12, EXACTLY));
+                break;
+        }
+
+        View child0 = mRecyclerView.getChildAt(0);
+        assertThat(getSize(child0, mTestOrientation), is(15));
+
+        View child1 = mRecyclerView.getChildAt(1);
+        assertThat(getSize(child1, mTestOrientation), is(15));
+
+        assertThat(mHelper.getDecoratedStart(child0), is(0));
+        assertThat(mHelper.getDecoratedStart(child1), is(15));
+
+        assertThat(mHelper.getDecoratedEnd(child0), is(15));
+        assertThat(mHelper.getDecoratedEnd(child1), is(30));
+
+        assertThat(mHelper.getDecoratedMeasurementInOther(child0),
+                is(Math.max(10, mBehavior1Size)));
+        assertThat(mHelper.getDecoratedMeasurementInOther(child1),
+                is(Math.max(10, mBehavior1Size)));
+
+        assertThat(getSize(mRecyclerView, mTestOrientation), is(45));
+        assertThat(getSize(mRecyclerView, 1 - mTestOrientation), is(Math.max(10, mBehavior1Size)));
+    }
+
+    @Test
+    public void testAllChildrenWrapContentInOtherDirection() throws Throwable {
+        MeasureBehavior behavior1;
+        behavior1 = new MeasureBehavior(mBehavior1Size, mBehavior1Size, WRAP_CONTENT, WRAP_CONTENT);
+
+        MeasureBehavior behavior2 = new MeasureBehavior(
+                mTestOrientation == HORIZONTAL ? 15 : 10,
+                mTestOrientation == VERTICAL ? 15 : 10, WRAP_CONTENT, WRAP_CONTENT);
+        WrapContentAdapter adapter = new WrapContentAdapter(behavior1, behavior2);
+
+        mRecyclerView.setAdapter(adapter);
+        setRecyclerView(mRecyclerView);
+        mRecyclerView.waitUntilLayout();
+        final int parentSize = getSize((View) mRecyclerView.getParent(), mTestOrientation);
+        if (mUnlimited) {
+            assertThat(behavior1.getSpec(0, mTestOrientation),
+                    MeasureSpecMatcher.mode(UNSPECIFIED));
+            assertThat(behavior2.getSpec(0, mTestOrientation),
+                    MeasureSpecMatcher.mode(UNSPECIFIED));
+        } else {
+            int[] borders = GridLayoutManager.calculateItemBorders(null,
+                    mConfig.mSpanCount, parentSize);
+            assertThat(behavior1.getSpec(0, mTestOrientation),
+                    MeasureSpecMatcher.is(borders[1] - borders[0], AT_MOST));
+            assertThat(behavior2.getSpec(0, mTestOrientation),
+                    MeasureSpecMatcher.is(borders[2] - borders[1], AT_MOST));
+        }
+
+        switch (mBehavior1Size) {
+            case 10:
+                assertThat(behavior1.measureSpecs.size(), is(1));
+                assertThat(behavior2.measureSpecs.size(), is(1));
+                break;
+            case 8:
+                assertThat(behavior2.measureSpecs.size(), is(1));
+                assertThat(behavior1.measureSpecs.size(), is(2));
+                assertThat(behavior1.getSpec(1, mTestOrientation),
+                        MeasureSpecMatcher.is(15, AT_MOST));
+                assertThat(behavior1.getSpec(1, mConfig.mOrientation),
+                        MeasureSpecMatcher.is(10, EXACTLY));
+                break;
+            case 12:
+                assertThat(behavior1.measureSpecs.size(), is(1));
+                assertThat(behavior2.measureSpecs.size(), is(2));
+                assertThat(behavior2.getSpec(1, mTestOrientation),
+                        MeasureSpecMatcher.is(15, AT_MOST));
+                assertThat(behavior2.getSpec(1, mConfig.mOrientation),
+                        MeasureSpecMatcher.is(12, EXACTLY));
+                break;
+        }
+
+        View child0 = mRecyclerView.getChildAt(0);
+        assertThat(getSize(child0, mTestOrientation), is(mBehavior1Size));
+
+        View child1 = mRecyclerView.getChildAt(1);
+        assertThat(getSize(child1, mTestOrientation), is(15));
+
+        assertThat(mHelper.getDecoratedStart(child0), is(0));
+        assertThat(mHelper.getDecoratedStart(child1), is(15));
+
+        assertThat(mHelper.getDecoratedEnd(child0), is(mBehavior1Size));
+        assertThat(mHelper.getDecoratedEnd(child1), is(30));
+
+        assertThat(mHelper.getDecoratedMeasurementInOther(child0),
+                is(Math.max(10, mBehavior1Size)));
+        assertThat(mHelper.getDecoratedMeasurementInOther(child1),
+                is(Math.max(10, mBehavior1Size)));
+
+        assertThat(getSize(mRecyclerView, mTestOrientation), is(45));
+        assertThat(getSize(mRecyclerView, 1 - mTestOrientation), is(Math.max(10, mBehavior1Size)));
+    }
+
+    private RecyclerView.LayoutManager createFromConfig() {
+        return new GridLayoutManager(getActivity(), mConfig.mSpanCount,
+                mConfig.mOrientation, mConfig.mReverseLayout);
+    }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerBaseConfigSetTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerBaseConfigSetTest.java
index 37b9821..73e913c 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerBaseConfigSetTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerBaseConfigSetTest.java
@@ -17,25 +17,28 @@
 package android.support.v7.widget;
 
 
-import org.junit.After;
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
 import android.graphics.Rect;
-import android.support.test.InstrumentationRegistry;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.util.Log;
 import android.view.View;
+import android.view.ViewGroup;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
 import static android.support.v7.widget.LayoutState.LAYOUT_END;
 import static android.support.v7.widget.LayoutState.LAYOUT_START;
 import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
-import static org.junit.Assert.*;
+import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.ViewGroup.LayoutParams.FILL_PARENT;
 
 /**
  * Tests that rely on the basic configuration and does not do any additions / removals
@@ -52,7 +55,11 @@
 
     @Parameterized.Parameters(name = "{0}")
     public static List<Config> configs() throws CloneNotSupportedException {
-        return createBaseVariations();
+        List<Config> result = new ArrayList<>();
+        for (Config config : createBaseVariations()) {
+            result.add(config);
+        }
+        return result;
     }
 
     @Test
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerPrepareForDropTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerPrepareForDropTest.java
index 773b253..27c93fc 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerPrepareForDropTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerPrepareForDropTest.java
@@ -105,11 +105,11 @@
                     int position) {
                 super.onBindViewHolder(holder, position);
                 if (config.mOrientation == HORIZONTAL) {
-                    final int base = mRecyclerView.getWidth() / 5;
+                    final int base = mLayoutManager.getWidth() / 5;
                     final int itemRand = holder.mBoundItem.mText.hashCode() % base;
                     holder.itemView.setMinimumWidth(base + itemRand);
                 } else {
-                    final int base = mRecyclerView.getHeight() / 5;
+                    final int base = mLayoutManager.getHeight() / 5;
                     final int itemRand = holder.mBoundItem.mText.hashCode() % base;
                     holder.itemView.setMinimumHeight(base + itemRand);
                 }
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerWrapContentTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerWrapContentTest.java
new file mode 100644
index 0000000..52c27bb
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerWrapContentTest.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import android.support.v4.view.ViewCompat;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.Gravity;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static android.support.v7.widget.BaseLinearLayoutManagerTest.Config;
+import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
+import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
+
+@RunWith(Parameterized.class)
+@MediumTest
+public class LinearLayoutManagerWrapContentTest extends BaseWrapContentTest {
+
+    Config mConfig;
+
+    public LinearLayoutManagerWrapContentTest(Config config,
+            WrapContentConfig wrapContentConfig) {
+        super(wrapContentConfig);
+        mConfig = config;
+    }
+
+    @Test
+    public void deletion() throws Throwable {
+        testScenerio(new Scenario(
+                new Step() {
+                    @Override
+                    void onRun() throws Throwable {
+                        mTestAdapter.deleteAndNotify(3, 3);
+                    }
+                },
+                new Step() {
+                    @Override
+                    void onRun() throws Throwable {
+                        mTestAdapter.deleteAndNotify(3, 3);
+                    }
+                },
+                new Step() {
+                    @Override
+                    void onRun() throws Throwable {
+                        mTestAdapter.deleteAndNotify(1, 2);
+                    }
+                }) {
+        });
+    }
+
+    @Test
+    public void addition() throws Throwable {
+        testScenerio(new Scenario(
+                new Step() {
+                    @Override
+                    void onRun() throws Throwable {
+                        mTestAdapter.addAndNotify(1, 2);
+                    }
+                }
+                ,
+                new Step() {
+                    @Override
+                    void onRun() throws Throwable {
+                        mTestAdapter.addAndNotify(0, 2);
+                    }
+                },
+                new Step() {
+                    @Override
+                    void onRun() throws Throwable {
+                        mTestAdapter.addAndNotify(6, 3);
+                    }
+                }
+        ) {
+            @Override
+            public int getSeedAdapterSize() {
+                return 2;
+            }
+        });
+    }
+
+    @Parameterized.Parameters(name = "{0} {1}")
+    public static Iterable<Object[]> data() {
+        List<Object[]> params = new ArrayList<>();
+        for (int orientation : new int[]{VERTICAL, HORIZONTAL}) {
+            for (boolean reverseLayout : new boolean[]{false, true}) {
+                for (boolean stackFromBottom : new boolean[]{false, true}) {
+                    params.add(
+                            new Object[]{
+                                    new Config(orientation, reverseLayout, stackFromBottom),
+                                    new WrapContentConfig(false, false)
+                            }
+                    );
+                    params.add(
+                            new Object[]{
+                                    new Config(orientation, reverseLayout, stackFromBottom),
+                                    new WrapContentConfig(HORIZONTAL == orientation,
+                                            VERTICAL == orientation)
+                            }
+                    );
+                }
+            }
+        }
+        return params;
+    }
+
+    @Override
+    RecyclerView.LayoutManager createLayoutManager() {
+        return createFromConfig();
+    }
+
+    private LinearLayoutManager createFromConfig() {
+        LinearLayoutManager llm = new LinearLayoutManager(getActivity(), mConfig.mOrientation,
+                mConfig.mReverseLayout);
+        llm.setStackFromEnd(mConfig.mStackFromEnd);
+        return llm;
+    }
+
+    @Override
+    protected int getVerticalGravity(RecyclerView.LayoutManager layoutManager) {
+        if (mConfig.mOrientation == HORIZONTAL) {
+            return Gravity.TOP;
+        }
+        if (mConfig.mReverseLayout ^ mConfig.mStackFromEnd) {
+            return Gravity.BOTTOM;
+        }
+        return Gravity.TOP;
+    }
+
+    @Override
+    protected int getHorizontalGravity(RecyclerView.LayoutManager layoutManager) {
+        boolean rtl = layoutManager.getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL;
+        if (mConfig.mOrientation == VERTICAL) {
+            if (rtl) {
+                return Gravity.RIGHT;
+            }
+            return Gravity.LEFT;
+        }
+        boolean end = mConfig.mReverseLayout ^ mConfig.mStackFromEnd;
+        if (rtl ^ end) {
+            return Gravity.RIGHT;
+        }
+        return Gravity.LEFT;
+    }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerWrapContentWithAspectRatioTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerWrapContentWithAspectRatioTest.java
new file mode 100644
index 0000000..2bb0750
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerWrapContentWithAspectRatioTest.java
@@ -0,0 +1,216 @@
+/*
+ * 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 org.hamcrest.CoreMatchers;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import android.graphics.Color;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
+import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+@RunWith(Parameterized.class)
+@MediumTest
+public class LinearLayoutManagerWrapContentWithAspectRatioTest
+        extends BaseWrapContentWithAspectRatioTest {
+
+    final LinearLayoutManagerTest.Config mConfig;
+    final float mRatio;
+
+    public LinearLayoutManagerWrapContentWithAspectRatioTest(
+            BaseLinearLayoutManagerTest.Config config,
+            BaseWrapContentTest.WrapContentConfig wrapContentConfig,
+            float ratio) {
+        super(wrapContentConfig);
+        mConfig = config;
+        mRatio = ratio;
+    }
+
+    @Parameterized.Parameters(name = "{0} {1} ratio:{2}")
+    public static Iterable<Object[]> data() {
+        List<Object[]> params = new ArrayList<>();
+        for (float ratio : new float[]{.5f, 1f, 2f}) {
+            for (int orientation : new int[]{VERTICAL, HORIZONTAL}) {
+                for (boolean reverseLayout : new boolean[]{false, true}) {
+                    for (boolean stackFromBottom : new boolean[]{false, true}) {
+                        params.add(
+                                new Object[]{
+                                        new BaseLinearLayoutManagerTest.Config(orientation,
+                                                reverseLayout, stackFromBottom),
+                                        new BaseWrapContentTest.WrapContentConfig(
+                                                false, false),
+                                        ratio
+                                }
+                        );
+                        params.add(
+                                new Object[]{
+                                        new BaseLinearLayoutManagerTest.Config(orientation,
+                                                reverseLayout, stackFromBottom),
+                                        new BaseWrapContentTest.WrapContentConfig(
+                                                HORIZONTAL == orientation,
+                                                VERTICAL == orientation),
+                                        ratio
+                                }
+                        );
+                        params.add(
+                                new Object[]{
+                                        new BaseLinearLayoutManagerTest.Config(orientation,
+                                                reverseLayout, stackFromBottom),
+                                        new BaseWrapContentTest.WrapContentConfig(
+                                                VERTICAL == orientation,
+                                                HORIZONTAL == orientation),
+                                        ratio
+                                }
+                        );
+                        params.add(
+                                new Object[]{
+                                        new BaseLinearLayoutManagerTest.Config(orientation,
+                                                reverseLayout, stackFromBottom),
+                                        new BaseWrapContentTest.WrapContentConfig(
+                                                true, true),
+                                        ratio
+                                }
+                        );
+                    }
+                }
+            }
+        }
+        return params;
+    }
+
+    @Test
+    public void wrapContentAffectsOtherOrientation() throws Throwable {
+        TestedFrameLayout.FullControlLayoutParams
+                wrapContent = new TestedFrameLayout.FullControlLayoutParams(
+                ViewGroup.LayoutParams.WRAP_CONTENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT);
+        int testOrientation = mConfig.mOrientation == VERTICAL ? HORIZONTAL : VERTICAL;
+        boolean unlimitedSize = false;
+        if (mWrapContentConfig.isUnlimitedHeight()) {
+            wrapContent.hSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+            unlimitedSize = testOrientation == VERTICAL;
+        }
+        if (mWrapContentConfig.isUnlimitedWidth()) {
+            wrapContent.wSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+            unlimitedSize |= testOrientation == HORIZONTAL;
+        }
+
+        RecyclerView.LayoutManager layoutManager = createFromConfig();
+        BaseWrapContentTest.WrappedRecyclerView
+                recyclerView = new BaseWrapContentTest.WrappedRecyclerView(getActivity());
+        recyclerView.setBackgroundColor(Color.rgb(0, 0, 255));
+        recyclerView.setLayoutManager(layoutManager);
+        recyclerView.setLayoutParams(wrapContent);
+
+        AspectRatioMeasureBehavior behavior1 = new AspectRatioMeasureBehavior(10, 10,
+                WRAP_CONTENT, WRAP_CONTENT).aspectRatio(testOrientation, mRatio);
+        AspectRatioMeasureBehavior behavior2 = new AspectRatioMeasureBehavior(15, 15,
+                testOrientation == HORIZONTAL ? MATCH_PARENT : WRAP_CONTENT,
+                testOrientation == VERTICAL ? MATCH_PARENT : WRAP_CONTENT)
+                .aspectRatio(testOrientation, mRatio);
+        AspectRatioMeasureBehavior behavior3 = new AspectRatioMeasureBehavior(8, 8,
+                testOrientation == HORIZONTAL ? MATCH_PARENT : WRAP_CONTENT,
+                testOrientation == VERTICAL ? MATCH_PARENT : WRAP_CONTENT)
+                .aspectRatio(testOrientation, mRatio);
+
+        WrapContentAdapter adapter = new WrapContentAdapter(
+                behavior1, behavior2, behavior3
+        );
+
+        recyclerView.setAdapter(adapter);
+        setRecyclerView(recyclerView);
+        recyclerView.waitUntilLayout();
+
+        int parentDim = getSize((View) recyclerView.getParent(), testOrientation);
+
+        View itemView1 = recyclerView.findViewHolderForAdapterPosition(0).itemView;
+        assertThat("first child test size", getSize(itemView1, testOrientation),
+                CoreMatchers.is(10));
+        assertThat("first child dependant size", getSize(itemView1, mConfig.mOrientation),
+                CoreMatchers.is(behavior1.getSecondary(10)));
+
+        View itemView2 = recyclerView.findViewHolderForAdapterPosition(1).itemView;
+        assertThat("second child test size", getSize(itemView2, testOrientation),
+                CoreMatchers.is(15));
+        assertThat("second child dependant size", getSize(itemView2, mConfig.mOrientation),
+                CoreMatchers.is(behavior2.getSecondary(15)));
+
+        View itemView3 = recyclerView.findViewHolderForAdapterPosition(2).itemView;
+        assertThat("third child test size", getSize(itemView3, testOrientation),
+                CoreMatchers.is(15));
+        assertThat("third child dependant size", getSize(itemView3, mConfig.mOrientation),
+                CoreMatchers.is(behavior3.getSecondary(15)));
+
+        assertThat("it should be measured only once", behavior1.measureSpecs.size(),
+                CoreMatchers.is(1));
+        if (unlimitedSize) {
+            assertThat(behavior1.getSpec(0, testOrientation),
+                    MeasureSpecMatcher.is(0, View.MeasureSpec.UNSPECIFIED));
+        } else {
+            assertThat(behavior1.getSpec(0, testOrientation),
+                    MeasureSpecMatcher.is(parentDim, View.MeasureSpec.AT_MOST));
+        }
+
+        assertThat("it should be measured once", behavior2.measureSpecs.size(), CoreMatchers.is(1));
+        if (unlimitedSize) {
+            assertThat(behavior2.getSpec(0, testOrientation),
+                    MeasureSpecMatcher.is(0, View.MeasureSpec.UNSPECIFIED));
+        } else {
+            assertThat(behavior2.getSpec(0, testOrientation),
+                    MeasureSpecMatcher.is(parentDim, View.MeasureSpec.AT_MOST));
+        }
+
+        assertThat("it should be measured twice", behavior3.measureSpecs.size(),
+                CoreMatchers.is(2));
+        if (unlimitedSize) {
+            assertThat(behavior3.getSpec(0, testOrientation),
+                    MeasureSpecMatcher.is(0, View.MeasureSpec.UNSPECIFIED));
+        } else {
+            assertThat(behavior3.getSpec(0, testOrientation),
+                    MeasureSpecMatcher.is(parentDim, View.MeasureSpec.AT_MOST));
+        }
+
+        assertThat(behavior3.getSpec(1, testOrientation),
+                MeasureSpecMatcher.is(15, View.MeasureSpec.EXACTLY));
+        final int totalScrollSize = getSize(itemView1, mConfig.mOrientation)
+                + getSize(itemView2, mConfig.mOrientation)
+                + getSize(itemView3, mConfig.mOrientation);
+        assertThat("RecyclerView should wrap its content in the scroll direction",
+                getSize(mRecyclerView, mConfig.mOrientation), CoreMatchers.is(totalScrollSize));
+        assertThat("RecyclerView should wrap its content in the scroll direction",
+                getSize(mRecyclerView, testOrientation), CoreMatchers.is(15));
+    }
+
+    private LinearLayoutManager createFromConfig() {
+        LinearLayoutManager llm = new LinearLayoutManager(getActivity(), mConfig.mOrientation,
+                mConfig.mReverseLayout);
+        llm.setStackFromEnd(mConfig.mStackFromEnd);
+        return llm;
+    }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/LoggingItemAnimator.java b/v7/recyclerview/tests/src/android/support/v7/widget/LoggingItemAnimator.java
index 9bbdd4b..b1bc21d 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/LoggingItemAnimator.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/LoggingItemAnimator.java
@@ -53,11 +53,33 @@
         return false;
     }
 
+    @NonNull
+    @Override
+    public ItemHolderInfo recordPreLayoutInformation(@NonNull RecyclerView.State state,
+            @NonNull RecyclerView.ViewHolder viewHolder,
+            @AdapterChanges int changeFlags, @NonNull List<Object> payloads) {
+        BaseRecyclerViewAnimationsTest.LoggingInfo
+                loggingInfo = new BaseRecyclerViewAnimationsTest.LoggingInfo(viewHolder, changeFlags, payloads);
+        return loggingInfo;
+    }
+
+    @NonNull
+    @Override
+    public ItemHolderInfo recordPostLayoutInformation(@NonNull RecyclerView.State state,
+            @NonNull RecyclerView.ViewHolder viewHolder) {
+        BaseRecyclerViewAnimationsTest.LoggingInfo
+                loggingInfo = new BaseRecyclerViewAnimationsTest.LoggingInfo(viewHolder, 0, null);
+        return loggingInfo;
+    }
+
+
     @Override
     public boolean animateDisappearance(@NonNull RecyclerView.ViewHolder viewHolder,
             @NonNull ItemHolderInfo preLayoutInfo, ItemHolderInfo postLayoutInfo) {
         mAnimateDisappearanceList
-                .add(new AnimateDisappearance(viewHolder, null, null));
+                .add(new AnimateDisappearance(viewHolder,
+                        (BaseRecyclerViewAnimationsTest.LoggingInfo) preLayoutInfo,
+                        (BaseRecyclerViewAnimationsTest.LoggingInfo) postLayoutInfo));
         return super.animateDisappearance(viewHolder, preLayoutInfo, postLayoutInfo);
     }
 
@@ -66,7 +88,9 @@
             ItemHolderInfo preLayoutInfo,
             @NonNull ItemHolderInfo postLayoutInfo) {
         mAnimateAppearanceList
-                .add(new AnimateAppearance(viewHolder, null, null));
+                .add(new AnimateAppearance(viewHolder,
+                        (BaseRecyclerViewAnimationsTest.LoggingInfo) preLayoutInfo,
+                        (BaseRecyclerViewAnimationsTest.LoggingInfo) postLayoutInfo));
         return super.animateAppearance(viewHolder, preLayoutInfo, postLayoutInfo);
     }
 
@@ -75,7 +99,9 @@
             @NonNull ItemHolderInfo preInfo,
             @NonNull ItemHolderInfo postInfo) {
         mAnimatePersistenceList
-                .add(new AnimatePersistence(viewHolder, null, null));
+                .add(new AnimatePersistence(viewHolder,
+                        (BaseRecyclerViewAnimationsTest.LoggingInfo) preInfo,
+                        (BaseRecyclerViewAnimationsTest.LoggingInfo) postInfo));
         return super.animatePersistence(viewHolder, preInfo, postInfo);
     }
 
@@ -84,7 +110,9 @@
             @NonNull RecyclerView.ViewHolder newHolder, @NonNull ItemHolderInfo preInfo,
             @NonNull ItemHolderInfo postInfo) {
         mAnimateChangeList
-                .add(new AnimateChange(oldHolder, newHolder, null, null));
+                .add(new AnimateChange(oldHolder, newHolder,
+                        (BaseRecyclerViewAnimationsTest.LoggingInfo) preInfo,
+                        (BaseRecyclerViewAnimationsTest.LoggingInfo) postInfo));
         return super.animateChange(oldHolder, newHolder, preInfo, postInfo);
     }
 
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java
index 25bb8ae..9ea61fd 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java
@@ -20,6 +20,7 @@
 import org.junit.runner.RunWith;
 
 import android.graphics.Rect;
+import android.support.annotation.NonNull;
 import android.os.Debug;
 import android.support.annotation.NonNull;
 import android.support.test.runner.AndroidJUnit4;
@@ -797,8 +798,8 @@
         mLayoutManager.waitForLayout(2);
     }
 
-    private void testChangeWithPayload(final boolean supportsChangeAnim, final boolean canReUse,
-            Object[][] notifyPayloads, Object[][] expectedPayloadsInOnBind)
+    private void testChangeWithPayload(final boolean supportsChangeAnim,
+            final boolean canReUse, Object[][] notifyPayloads, Object[][] expectedPayloadsInOnBind)
             throws Throwable {
         final List<Object> expectedPayloads = new ArrayList<Object>();
         final int changedIndex = 3;
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
index 15a9012..bedc506 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
@@ -27,12 +27,13 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
 import android.support.v4.view.ViewCompat;
 import android.support.v7.util.TouchUtils;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.util.Log;
 import android.view.Gravity;
-import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
@@ -50,11 +51,10 @@
 import java.util.concurrent.atomic.AtomicInteger;
 
 import static android.support.v7.widget.RecyclerView.NO_POSITION;
-import static android.support.v7.widget.RecyclerView.SCROLL_STATE_IDLE;
 import static android.support.v7.widget.RecyclerView.SCROLL_STATE_DRAGGING;
+import static android.support.v7.widget.RecyclerView.SCROLL_STATE_IDLE;
 import static android.support.v7.widget.RecyclerView.SCROLL_STATE_SETTLING;
 import static android.support.v7.widget.RecyclerView.getChildViewHolderInt;
-import android.support.test.runner.AndroidJUnit4;
 import static org.junit.Assert.*;
 
 @RunWith(AndroidJUnit4.class)
@@ -208,15 +208,25 @@
         });
         assertTrue(c.hasFocus());
         freezeLayout(true);
-        getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
+        focusSearch(recyclerView, c, View.FOCUS_DOWN);
         assertEquals("onFocusSearchFailed should not be called when layout is frozen",
                 0, focusSearchCalled.get());
         freezeLayout(false);
-        getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
+        focusSearch(recyclerView, c, View.FOCUS_DOWN);
         assertTrue(focusLatch.await(2, TimeUnit.SECONDS));
         assertEquals(1, focusSearchCalled.get());
     }
 
+    public void focusSearch(final ViewGroup parent, final View focused, final int direction)
+            throws Throwable {
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                parent.focusSearch(focused, direction);
+            }
+        });
+    }
+
     @Test
     public void frozenAndChangeAdapter() throws Throwable {
         RecyclerView recyclerView = new RecyclerView(getActivity());
@@ -1261,13 +1271,16 @@
     }
 
     @Test
-    public void accessRecyclerOnOnMeasure() throws Throwable {
-        accessRecyclerOnOnMeasureTest(false);
-        removeRecyclerView();
+    public void aAccessRecyclerOnOnMeasureWithPredictive() throws Throwable {
         accessRecyclerOnOnMeasureTest(true);
     }
 
     @Test
+    public void accessRecyclerOnOnMeasureWithoutPredictive() throws Throwable {
+        accessRecyclerOnOnMeasureTest(false);
+    }
+
+    @Test
     public void smoothScrollWithRemovedItemsAndRemoveItem() throws Throwable {
         smoothScrollTest(true);
     }
@@ -1481,8 +1494,10 @@
                         assertNotNull(view);
                         assertEquals(i, getPosition(view));
                     }
-                    assertEquals(state.toString(),
-                            expectedOnMeasureStateCount.get(), state.getItemCount());
+                    if (!state.isPreLayout()) {
+                        assertEquals(state.toString(),
+                                expectedOnMeasureStateCount.get(), state.getItemCount());
+                    }
                 } catch (Throwable t) {
                     postExceptionToInstrumentation(t);
                 }
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerBaseConfigSetTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerBaseConfigSetTest.java
index 7047cde..15331b5 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerBaseConfigSetTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerBaseConfigSetTest.java
@@ -61,20 +61,46 @@
 
     @Test
     public void rTL() throws Throwable {
-        rtlTest(false);
+        rtlTest(false, false);
     }
 
     @Test
     public void rTLChangeAfter() throws Throwable {
-        rtlTest(true);
+        rtlTest(true, false);
     }
 
-    void rtlTest(boolean changeRtlAfter) throws Throwable {
+    @Test
+    public void rTLItemWrapContent() throws Throwable {
+        rtlTest(false, true);
+    }
+
+    @Test
+    public void rTLChangeAfterItemWrapContent() throws Throwable {
+        rtlTest(true, true);
+    }
+
+    void rtlTest(boolean changeRtlAfter, final boolean wrapContent) throws Throwable {
         if (mConfig.mSpanCount == 1) {
             mConfig.mSpanCount = 2;
         }
         String logPrefix = mConfig + ", changeRtlAfterLayout:" + changeRtlAfter;
-        setupByConfig(mConfig.itemCount(5));
+        setupByConfig(mConfig.itemCount(5),
+                new GridTestAdapter(mConfig.mItemCount, mConfig.mOrientation) {
+                    @Override
+                    public void onBindViewHolder(TestViewHolder holder,
+                            int position) {
+                        super.onBindViewHolder(holder, position);
+                        if (wrapContent) {
+                            if (mOrientation == HORIZONTAL) {
+                                holder.itemView.getLayoutParams().height
+                                        = RecyclerView.LayoutParams.WRAP_CONTENT;
+                            } else {
+                                holder.itemView.getLayoutParams().width
+                                        = RecyclerView.LayoutParams.MATCH_PARENT;
+                            }
+                        }
+                    }
+                });
         if (changeRtlAfter) {
             waitFirstLayout();
             mLayoutManager.expectLayouts(1);
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerTest.java
index 006ab54..07d036a 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerTest.java
@@ -61,6 +61,7 @@
 import static android.support.v7.widget.StaggeredGridLayoutManager.LayoutParams;
 import static org.junit.Assert.*;
 
+
 @MediumTest
 public class StaggeredGridLayoutManagerTest extends BaseStaggeredGridLayoutManagerTest {
 
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerWrapContentTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerWrapContentTest.java
new file mode 100644
index 0000000..5b04302
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerWrapContentTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import android.graphics.Rect;
+import android.view.Gravity;
+import android.view.View;
+
+import static android.support.v7.widget.StaggeredGridLayoutManager.VERTICAL;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.support.v7.widget.BaseWrapContentWithAspectRatioTest.*;
+
+@RunWith(JUnit4.class)
+public class StaggeredGridLayoutManagerWrapContentTest extends BaseWrapContentTest {
+
+    public StaggeredGridLayoutManagerWrapContentTest() {
+        super(new WrapContentConfig(false, false));
+    }
+
+    @Test
+    public void testSimple() throws Throwable {
+        TestedFrameLayout.FullControlLayoutParams lp =
+                mWrapContentConfig.toLayoutParams(WRAP_CONTENT, WRAP_CONTENT);
+        WrapContentAdapter adapter = new WrapContentAdapter(
+                new MeasureBehavior(10, 10, WRAP_CONTENT, WRAP_CONTENT),
+                new MeasureBehavior(10, 15, WRAP_CONTENT, WRAP_CONTENT),
+                new MeasureBehavior(10, 20, WRAP_CONTENT, WRAP_CONTENT),
+                new MeasureBehavior(20, 10, WRAP_CONTENT, WRAP_CONTENT)
+        );
+        Rect[] expected = new Rect[] {
+                new Rect(0, 0, 10, 10),
+                new Rect(20, 0, 30, 15),
+                new Rect(40, 0, 50, 20),
+                new Rect(0, 10, 20, 20)
+        };
+        layoutAndCheck(lp, adapter, expected, 60, 20);
+    }
+
+    @Test
+    public void testUnspecifiedWidth() throws Throwable {
+        TestedFrameLayout.FullControlLayoutParams lp =
+                mWrapContentConfig.toLayoutParams(WRAP_CONTENT, WRAP_CONTENT);
+        lp.wSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+        WrapContentAdapter adapter = new WrapContentAdapter(
+                new MeasureBehavior(2000, 10, WRAP_CONTENT, WRAP_CONTENT),
+                new MeasureBehavior(500, 15, WRAP_CONTENT, WRAP_CONTENT),
+                new MeasureBehavior(400, 20, WRAP_CONTENT, WRAP_CONTENT),
+                new MeasureBehavior(50, 10, MATCH_PARENT, WRAP_CONTENT)
+        );
+        Rect[] expected = new Rect[] {
+                new Rect(0, 0, 2000, 10),
+                new Rect(2000, 0, 2500, 15),
+                new Rect(4000, 0, 4400, 20),
+                new Rect(0, 10, 2000, 20)
+        };
+        layoutAndCheck(lp, adapter, expected, 6000, 20);
+    }
+
+    @Test
+    public void testUnspecifiedHeight() throws Throwable {
+        TestedFrameLayout.FullControlLayoutParams lp =
+                mWrapContentConfig.toLayoutParams(WRAP_CONTENT, WRAP_CONTENT);
+        lp.hSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+        WrapContentAdapter adapter = new WrapContentAdapter(
+                new MeasureBehavior(10, 4000, WRAP_CONTENT, WRAP_CONTENT),
+                new MeasureBehavior(10, 5500, WRAP_CONTENT, WRAP_CONTENT),
+                new MeasureBehavior(10, 3000, WRAP_CONTENT, WRAP_CONTENT),
+                new MeasureBehavior(20, 100, WRAP_CONTENT, WRAP_CONTENT)
+        );
+        Rect[] expected = new Rect[] {
+                new Rect(0, 0, 10, 4000),
+                new Rect(20, 0, 30, 5500),
+                new Rect(40, 0, 50, 3000),
+                new Rect(40, 3000, 60, 3100)
+        };
+        layoutAndCheck(lp, adapter, expected, 60, 5500);
+    }
+
+    @Override
+    RecyclerView.LayoutManager createLayoutManager() {
+        return new StaggeredGridLayoutManager(3, VERTICAL);
+    }
+
+    @Override
+    protected int getVerticalGravity(RecyclerView.LayoutManager layoutManager) {
+        return Gravity.TOP;
+    }
+
+    @Override
+    protected int getHorizontalGravity(RecyclerView.LayoutManager layoutManager) {
+        return Gravity.LEFT;
+    }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/TestedFrameLayout.java b/v7/recyclerview/tests/src/android/support/v7/widget/TestedFrameLayout.java
index 18aeba4..040d001 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/TestedFrameLayout.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/TestedFrameLayout.java
@@ -19,8 +19,10 @@
 import android.content.Context;
 import android.support.v4.view.NestedScrollingParent;
 import android.support.v4.view.ViewCompat;
+import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.FrameLayout;
 
 public class TestedFrameLayout extends FrameLayout implements NestedScrollingParent {
@@ -37,6 +39,103 @@
     }
 
     @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        RecyclerView recyclerView = getRvChild();
+        if (recyclerView == null) {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+            return;
+        }
+        FullControlLayoutParams lp = (FullControlLayoutParams) recyclerView.getLayoutParams();
+        if (lp.wSpec == null && lp.hSpec == null) {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+            return;
+        }
+        final int childWidthMeasureSpec;
+        if (lp.wSpec != null) {
+            childWidthMeasureSpec = lp.wSpec;
+        } else if (lp.width == LayoutParams.MATCH_PARENT) {
+            final int width = Math.max(0, getMeasuredWidth()
+                    - lp.leftMargin - lp.rightMargin);
+            childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
+        } else {
+            childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
+                    lp.leftMargin + lp.rightMargin, lp.width);
+        }
+
+        final int childHeightMeasureSpec;
+        if (lp.hSpec != null) {
+            childHeightMeasureSpec = lp.hSpec;
+        } else if (lp.height == LayoutParams.MATCH_PARENT) {
+            final int height = Math.max(0, getMeasuredHeight()
+                    - lp.topMargin - lp.bottomMargin);
+            childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
+        } else {
+            childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
+                    lp.topMargin + lp.bottomMargin, lp.height);
+        }
+        recyclerView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+        if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY &&
+                MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) {
+            setMeasuredDimension(
+                    MeasureSpec.getSize(widthMeasureSpec),
+                    MeasureSpec.getSize(heightMeasureSpec)
+            );
+        } else {
+            setMeasuredDimension(
+                    chooseSize(widthMeasureSpec,
+                            recyclerView.getWidth() + getPaddingLeft() + getPaddingRight(),
+                            getMinimumWidth()),
+                    chooseSize(heightMeasureSpec,
+                            recyclerView.getHeight() + getPaddingTop() + getPaddingBottom(),
+                            getMinimumHeight()));
+        }
+    }
+
+    public static int chooseSize(int spec, int desired, int min) {
+        final int mode = View.MeasureSpec.getMode(spec);
+        final int size = View.MeasureSpec.getSize(spec);
+        switch (mode) {
+            case View.MeasureSpec.EXACTLY:
+                return size;
+            case View.MeasureSpec.AT_MOST:
+                return Math.min(size, desired);
+            case View.MeasureSpec.UNSPECIFIED:
+            default:
+                return Math.max(desired, min);
+        }
+    }
+
+
+    private RecyclerView getRvChild() {
+        for (int i = 0; i < getChildCount(); i++) {
+            if (getChildAt(i) instanceof RecyclerView) {
+                return (RecyclerView) getChildAt(i);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+        return p instanceof FullControlLayoutParams;
+    }
+
+    @Override
+    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+        return new FullControlLayoutParams(p);
+    }
+
+    @Override
+    public LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new FullControlLayoutParams(getContext(), attrs);
+    }
+
+    @Override
+    protected LayoutParams generateDefaultLayoutParams() {
+        return new FullControlLayoutParams(getWidth(), getHeight());
+    }
+
+    @Override
     public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
         // Always start nested scroll
         return mNestedFlingMode == TEST_NESTED_SCROLL_MODE_CONSUME
@@ -98,4 +197,30 @@
     public void setNestedFlingMode(int mode) {
         mNestedFlingMode = mode;
     }
+
+    public static class FullControlLayoutParams extends FrameLayout.LayoutParams {
+
+        Integer wSpec;
+        Integer hSpec;
+
+        public FullControlLayoutParams(Context c, AttributeSet attrs) {
+            super(c, attrs);
+        }
+
+        public FullControlLayoutParams(int width, int height) {
+            super(width, height);
+        }
+
+        public FullControlLayoutParams(ViewGroup.LayoutParams source) {
+            super(source);
+        }
+
+        public FullControlLayoutParams(FrameLayout.LayoutParams source) {
+            super(source);
+        }
+
+        public FullControlLayoutParams(MarginLayoutParams source) {
+            super(source);
+        }
+    }
 }
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/WrapContentBasicTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/WrapContentBasicTest.java
new file mode 100644
index 0000000..b485fa6
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/WrapContentBasicTest.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.v7.widget;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.content.Context;
+import android.support.annotation.Nullable;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.AndroidTestCase;
+import android.view.Display;
+import android.view.View;
+import android.view.ViewGroup;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(AndroidJUnit4.class)
+public class WrapContentBasicTest extends AndroidTestCase {
+
+    private WrapContentLayoutManager mLayoutManager;
+    private RecyclerView mRecyclerView;
+    private WrapAdapter mAdapter;
+    private static int WRAP = View.MeasureSpec.makeMeasureSpec(10, View.MeasureSpec.AT_MOST);
+    private static int EXACT = View.MeasureSpec.makeMeasureSpec(10, View.MeasureSpec.EXACTLY);
+    private static int UNSPECIFIED = View.MeasureSpec
+            .makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        setContext(InstrumentationRegistry.getContext());
+        RecyclerView rv = new RecyclerView(getContext());
+        mRecyclerView = spy(rv);
+        mLayoutManager = spy(new WrapContentLayoutManager());
+        // working around a mockito issue
+        rv.mLayout = mLayoutManager;
+        mAdapter = spy(new WrapAdapter());
+        mRecyclerView.setLayoutManager(mLayoutManager);
+        mRecyclerView.setAdapter(mAdapter);
+    }
+
+    @After
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    public void testLayoutInOnMeasureWithoutPredictive() {
+        mLayoutManager.setAutoMeasureEnabled(true);
+        when(mLayoutManager.supportsPredictiveItemAnimations()).thenReturn(false);
+        mRecyclerView.onMeasure(WRAP, WRAP);
+        mRecyclerView.onMeasure(WRAP, WRAP);
+        mRecyclerView.onLayout(true, 0, 10, 10, 10);
+        verify(mLayoutManager, times(3))
+                .onLayoutChildren(mRecyclerView.mRecycler, mRecyclerView.mState);
+    }
+
+    @Test
+    public void dataChangeAfterMeasure() {
+        mLayoutManager.setAutoMeasureEnabled(true);
+        mRecyclerView.onMeasure(WRAP, WRAP);
+        mRecyclerView.onMeasure(WRAP, WRAP);
+        mAdapter.notifyItemChanged(1);
+        mRecyclerView.onLayout(true, 0, 10, 10, 10);
+        verify(mLayoutManager, times(3))
+                .onLayoutChildren(mRecyclerView.mRecycler, mRecyclerView.mState);
+    }
+
+    @Test
+    public void setDimensionsFromChildren() {
+        mLayoutManager.setAutoMeasureEnabled(true);
+        View[] children = createMockChildren(3);
+        mLayoutManager.setMeasuredDimensionFromChildren(WRAP, WRAP);
+        verify(mLayoutManager).setMeasuredDimension(children[0].getWidth(),
+                children[0].getHeight());
+    }
+
+    @Test
+    public void setDimensionsFromChildrenAnsSpec1() {
+        mLayoutManager.setAutoMeasureEnabled(true);
+        View[] children = createMockChildren(3);
+        int hSpec = View.MeasureSpec.makeMeasureSpec(111, View.MeasureSpec.EXACTLY);
+        mLayoutManager.setMeasuredDimensionFromChildren(WRAP, hSpec);
+        verify(mLayoutManager).setMeasuredDimension(children[0].getWidth(), 111);
+    }
+
+    @Test
+    public void setDimensionsFromChildrenAnsSpec2() {
+        mLayoutManager.setAutoMeasureEnabled(true);
+        View[] children = createMockChildren(3);
+        int wSpec = View.MeasureSpec.makeMeasureSpec(111, View.MeasureSpec.EXACTLY);
+        mLayoutManager.setMeasuredDimensionFromChildren(wSpec, WRAP);
+        verify(mLayoutManager).setMeasuredDimension(111, children[0].getHeight());
+    }
+
+    @Test
+    public void setDimensionsFromChildrenAnsSpec3() {
+        mLayoutManager.setAutoMeasureEnabled(true);
+        View[] children = createMockChildren(3);
+        children[0].layout(0, 0, 100, 100);
+        children[1].layout(-5, 0, 100, 100);
+        children[2].layout(-5, -10, 100, 100);
+        mLayoutManager.setMeasuredDimensionFromChildren(UNSPECIFIED, UNSPECIFIED);
+        verify(mLayoutManager).setMeasuredDimension(105, 110);
+    }
+
+    @Test
+    public void setDimensionsFromChildrenAnsSpec4() {
+        mLayoutManager.setAutoMeasureEnabled(true);
+        View[] children = createMockChildren(3);
+        children[0].layout(0, 0, 100, 100);
+        children[1].layout(-5, 0, 100, 100);
+        children[2].layout(-5, -10, 100, 100);
+        int atMost = View.MeasureSpec.makeMeasureSpec(95, View.MeasureSpec.AT_MOST);
+        mLayoutManager.setMeasuredDimensionFromChildren(atMost, atMost);
+        verify(mLayoutManager).setMeasuredDimension(95, 95);
+    }
+
+    @Test
+    public void setDimensionsFromChildrenAnsSpec5() {
+        mLayoutManager.setAutoMeasureEnabled(true);
+        View[] children = createMockChildren(3);
+        children[0].layout(0, 0, 100, 100);
+        children[1].layout(-5, 0, 100, 100);
+        children[2].layout(-5, -10, 100, 100);
+        when(mRecyclerView.getMinimumWidth()).thenReturn(250);
+        mLayoutManager.setMeasuredDimensionFromChildren(UNSPECIFIED, UNSPECIFIED);
+        verify(mLayoutManager).setMeasuredDimension(250, 110);
+
+        when(mRecyclerView.getMinimumWidth()).thenReturn(5);
+        mLayoutManager.setMeasuredDimensionFromChildren(UNSPECIFIED, UNSPECIFIED);
+        verify(mLayoutManager).setMeasuredDimension(105, 110);
+    }
+
+    @Test
+    public void setDimensionsFromChildrenAnsSpec6() {
+        mLayoutManager.setAutoMeasureEnabled(true);
+        View[] children = createMockChildren(3);
+        children[0].layout(0, 0, 100, 100);
+        children[1].layout(-5, 0, 100, 100);
+        children[2].layout(-5, -10, 100, 100);
+        when(mRecyclerView.getMinimumHeight()).thenReturn(250);
+        mLayoutManager.setMeasuredDimensionFromChildren(UNSPECIFIED, UNSPECIFIED);
+        verify(mLayoutManager).setMeasuredDimension(105, 250);
+
+        when(mRecyclerView.getMinimumHeight()).thenReturn(50);
+        mLayoutManager.setMeasuredDimensionFromChildren(UNSPECIFIED, UNSPECIFIED);
+        verify(mLayoutManager).setMeasuredDimension(105, 110);
+    }
+
+    private View[] createMockChildren(int count) {
+        View[] views = new View[count];
+        for (int i = 0; i < count; i++) {
+            View v = new View(getContext());
+            v.setLayoutParams(new RecyclerView.LayoutParams(1, 1));
+            views[i] = v;
+            when(mLayoutManager.getChildAt(i)).thenReturn(v);
+        }
+        when(mLayoutManager.getChildCount()).thenReturn(3);
+        return views;
+    }
+
+    public class WrapContentLayoutManager extends RecyclerView.LayoutManager {
+
+        @Override
+        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+
+        }
+
+        @Override
+        public RecyclerView.LayoutParams generateDefaultLayoutParams() {
+            return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+                    ViewGroup.LayoutParams.WRAP_CONTENT);
+        }
+    }
+
+    public class WrapAdapter extends RecyclerView.Adapter {
+
+        @Override
+        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+            return null;
+        }
+
+        @Override
+        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+
+        }
+
+        @Override
+        public int getItemCount() {
+            return 10;
+        }
+    }
+}