Merge "Rework background manager and add TestCases"
diff --git a/fragment/java/android/support/v4/app/BackStackRecord.java b/fragment/java/android/support/v4/app/BackStackRecord.java
index 19f949c..85ded71 100644
--- a/fragment/java/android/support/v4/app/BackStackRecord.java
+++ b/fragment/java/android/support/v4/app/BackStackRecord.java
@@ -748,7 +748,7 @@
         }
         if (!mAllowOptimization) {
             // Added fragments are added at the end to comply with prior behavior.
-            mManager.moveToState(mManager.mCurState);
+            mManager.moveToState(mManager.mCurState, true);
         }
     }
 
@@ -794,7 +794,7 @@
             }
         }
         if (!mAllowOptimization) {
-            mManager.moveToState(mManager.mCurState);
+            mManager.moveToState(mManager.mCurState, true);
         }
     }
 
diff --git a/fragment/java/android/support/v4/app/FragmentManager.java b/fragment/java/android/support/v4/app/FragmentManager.java
index 6ef6ed1..77325cb 100644
--- a/fragment/java/android/support/v4/app/FragmentManager.java
+++ b/fragment/java/android/support/v4/app/FragmentManager.java
@@ -16,6 +16,8 @@
 
 package android.support.v4.app;
 
+import static android.support.annotation.RestrictTo.Scope.GROUP_ID;
+
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources.NotFoundException;
@@ -62,8 +64,6 @@
 import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
 
-import static android.support.annotation.RestrictTo.Scope.GROUP_ID;
-
 /**
  * Static library support version of the framework's {@link android.app.FragmentManager}.
  * Used to write apps that run on platforms prior to Android 3.0.  When running
@@ -1554,11 +1554,24 @@
         }
     }
 
-    void moveToState(int newState) {
+    /**
+     * Changes the state of the fragment manager to {@code newState}. If the fragment manager
+     * changes state or {@code always} is {@code true}, any fragments within it have their
+     * states updated as well.
+     *
+     * @param newState The new state for the fragment manager
+     * @param always If {@code true}, all fragments update their state, even
+     *               if {@code newState} matches the current fragment manager's state.
+     */
+    void moveToState(int newState, boolean always) {
         if (mHost == null && newState != Fragment.INITIALIZING) {
             throw new IllegalStateException("No activity");
         }
 
+        if (!always && newState == mCurState) {
+            return;
+        }
+
         mCurState = newState;
 
         if (mActive != null) {
@@ -2129,7 +2142,7 @@
             // need to run something now
             FragmentTransition.startTransitions(this, records, isRecordPop, startIndex,
                     postponeIndex, true);
-            moveToState(mCurState);
+            moveToState(mCurState, true);
         }
 
         for (int recordNum = startIndex; recordNum < endIndex; recordNum++) {
@@ -2221,7 +2234,7 @@
             FragmentTransition.startTransitions(this, records, isRecordPop, 0, 1, true);
         }
         if (moveToState) {
-            moveToState(mCurState);
+            moveToState(mCurState, true);
         } else if (mActive != null) {
             final int numActive = mActive.size();
             for (int i = 0; i < numActive; i++) {
@@ -2811,26 +2824,26 @@
 
     public void dispatchCreate() {
         mStateSaved = false;
-        moveToState(Fragment.CREATED);
+        moveToState(Fragment.CREATED, false);
     }
 
     public void dispatchActivityCreated() {
         mStateSaved = false;
-        moveToState(Fragment.ACTIVITY_CREATED);
+        moveToState(Fragment.ACTIVITY_CREATED, false);
     }
 
     public void dispatchStart() {
         mStateSaved = false;
-        moveToState(Fragment.STARTED);
+        moveToState(Fragment.STARTED, false);
     }
 
     public void dispatchResume() {
         mStateSaved = false;
-        moveToState(Fragment.RESUMED);
+        moveToState(Fragment.RESUMED, false);
     }
 
     public void dispatchPause() {
-        moveToState(Fragment.STARTED);
+        moveToState(Fragment.STARTED, false);
     }
 
     public void dispatchStop() {
@@ -2839,21 +2852,21 @@
         // them.
         mStateSaved = true;
 
-        moveToState(Fragment.STOPPED);
+        moveToState(Fragment.STOPPED, false);
     }
 
     public void dispatchReallyStop() {
-        moveToState(Fragment.ACTIVITY_CREATED);
+        moveToState(Fragment.ACTIVITY_CREATED, false);
     }
 
     public void dispatchDestroyView() {
-        moveToState(Fragment.CREATED);
+        moveToState(Fragment.CREATED, false);
     }
 
     public void dispatchDestroy() {
         mDestroyed = true;
         execPendingActions();
-        moveToState(Fragment.INITIALIZING);
+        moveToState(Fragment.INITIALIZING, false);
         mHost = null;
         mContainer = null;
         mParent = null;
diff --git a/fragment/tests/java/android/support/v4/app/FragmentLifecycleTest.java b/fragment/tests/java/android/support/v4/app/FragmentLifecycleTest.java
index 94d0445..4c0c0a4 100644
--- a/fragment/tests/java/android/support/v4/app/FragmentLifecycleTest.java
+++ b/fragment/tests/java/android/support/v4/app/FragmentLifecycleTest.java
@@ -17,6 +17,19 @@
 
 package android.support.v4.app;
 
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNotSame;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertSame;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertNotEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
 import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
@@ -36,6 +49,7 @@
 import android.view.ViewGroup;
 import android.view.Window;
 import android.widget.TextView;
+
 import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
@@ -44,10 +58,6 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
-import static junit.framework.Assert.*;
-import static org.junit.Assert.assertNotEquals;
-import static org.mockito.Mockito.*;
-
 @RunWith(AndroidJUnit4.class)
 @MediumTest
 public class FragmentLifecycleTest {
@@ -540,6 +550,42 @@
         assertTrue(fragmentA.mCalledOnDestroy);
     }
 
+    /**
+     * Test to ensure that when dispatch* is called that the fragment manager
+     * doesn't cause the contained fragment states to change even if no state changes.
+     */
+    @Test
+    @UiThreadTest
+    public void noPrematureStateChange() throws Throwable {
+        FragmentController fc = startupFragmentController(null);
+        FragmentManager fm = fc.getSupportFragmentManager();
+
+        fm.beginTransaction()
+                .add(new StrictFragment(), "1")
+                .commitNow();
+
+        Parcelable savedState = shutdownFragmentController(fc);
+        fc = FragmentController.createController(
+                new HostCallbacks(mActivityRule.getActivity()));
+
+        fc.attachHost(null);
+        fc.dispatchCreate();
+        fc.dispatchActivityCreated();
+        fc.noteStateNotSaved();
+        fc.execPendingActions();
+        fc.doLoaderStart();
+        fc.dispatchStart();
+        fc.reportLoaderStart();
+        fc.dispatchResume();
+        fc.restoreAllState(savedState, (FragmentManagerNonConfig) null);
+        fc.dispatchResume();
+        fm = fc.getSupportFragmentManager();
+
+        StrictFragment fragment1 = (StrictFragment) fm.findFragmentByTag("1");
+
+        assertFalse(fragment1.mCalledOnResume);
+    }
+
     private void assertAnimationsMatch(FragmentManager fm, int enter, int exit, int popEnter,
             int popExit) {
         FragmentManagerImpl fmImpl = (FragmentManagerImpl) fm;
diff --git a/samples/Support7Demos/res/layout/appcompat_widgets_buttons.xml b/samples/Support7Demos/res/layout/appcompat_widgets_buttons.xml
index aa07328..1c60d90 100644
--- a/samples/Support7Demos/res/layout/appcompat_widgets_buttons.xml
+++ b/samples/Support7Demos/res/layout/appcompat_widgets_buttons.xml
@@ -16,8 +16,9 @@
 -->
 
 <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent">
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
 
     <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                   android:layout_width="match_parent"
@@ -77,6 +78,19 @@
                 android:text="Button (borderless + colored)"
                 style="@style/Widget.AppCompat.Button.Borderless.Colored"/>
 
+        <Button
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Button (colored)"
+                style="@style/Widget.AppCompat.Button.Colored"/>
+
+        <Button
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Button (colored + tinted)"
+                app:backgroundTint="#00FF00"
+                style="@style/Widget.AppCompat.Button.Colored"/>
+
         <RatingBar
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"/>
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatBackgroundHelper.java b/v7/appcompat/src/android/support/v7/widget/AppCompatBackgroundHelper.java
index 09e4a28..3fffb9f 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatBackgroundHelper.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatBackgroundHelper.java
@@ -155,10 +155,9 @@
     void applySupportBackgroundTint() {
         final Drawable background = mView.getBackground();
         if (background != null) {
-            if (Build.VERSION.SDK_INT == 21 && applyFrameworkTintUsingColorFilter(background)) {
-                // GradientDrawable doesn't implement setTintList on API 21, and since there is
-                // no nice way to unwrap DrawableContainers we have to blanket apply this
-                // on API 21. This needs to be called before the internal tints below so it takes
+            if (shouldApplyFrameworkTintUsingColorFilter()
+                    && applyFrameworkTintUsingColorFilter(background)) {
+                // This needs to be called before the internal tints below so it takes
                 // effect on any widgets using the compat tint on API 21 (EditText)
                 return;
             }
@@ -186,6 +185,23 @@
         applySupportBackgroundTint();
     }
 
+    private boolean shouldApplyFrameworkTintUsingColorFilter() {
+        final int sdk = Build.VERSION.SDK_INT;
+        if (sdk < 21) {
+            // API 19 and below doesn't have framework tint
+            return false;
+        } else if (sdk == 21) {
+            // GradientDrawable doesn't implement setTintList on API 21, and since there is
+            // no nice way to unwrap DrawableContainers we have to blanket apply this
+            // on API 21
+            return true;
+        } else {
+            // On API 22+, if we're using an internal compat background tint, we're also
+            // responsible for applying any custom tint set via the framework impl
+            return mInternalBackgroundTint != null;
+        }
+    }
+
     /**
      * Applies the framework background tint to a view, but using the compat method (ColorFilter)
      *
diff --git a/v7/appcompat/tests/res/layout/appcompat_button_activity.xml b/v7/appcompat/tests/res/layout/appcompat_button_activity.xml
index 72d6500..7708d0c 100644
--- a/v7/appcompat/tests/res/layout/appcompat_button_activity.xml
+++ b/v7/appcompat/tests/res/layout/appcompat_button_activity.xml
@@ -76,6 +76,13 @@
             android:text="@string/sample_text2"
             android:background="@drawable/test_background_green" />
 
+        <android.support.v7.widget.AppCompatButton
+            android:id="@+id/button_colored_untinted"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/sample_text2"
+            style="@style/Widget.AppCompat.Button.Colored"/>
+
     </LinearLayout>
 
 </ScrollView>
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 6c1f167..5080252 100644
--- a/v7/appcompat/tests/src/android/support/v7/testutils/TestUtils.java
+++ b/v7/appcompat/tests/src/android/support/v7/testutils/TestUtils.java
@@ -169,6 +169,36 @@
     }
 
     /**
+     * Checks whether the center pixel in the specified drawable is 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 assertCenterPixelOfColor(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 {
+            assertCenterPixelOfColor(failMessagePrefix, bitmap, color, allowedComponentVariance,
+                    throwExceptionIfFails);
+        } finally {
+            bitmap.recycle();
+        }
+    }
+
+    /**
      * Checks whether the center pixel in the specified bitmap is of the same specified color.
      *
      * In case there is a color mismatch, the behavior of this method depends on the
@@ -177,8 +207,7 @@
      * <code>Assert.fail</code> with detailed description of the mismatch.
      */
     public static void assertCenterPixelOfColor(String failMessagePrefix, @NonNull Bitmap bitmap,
-            @ColorInt int color,
-            int allowedComponentVariance, boolean throwExceptionIfFails) {
+            @ColorInt int color, int allowedComponentVariance, boolean throwExceptionIfFails) {
         final int centerX = bitmap.getWidth() / 2;
         final int centerY = bitmap.getHeight() / 2;
         final @ColorInt int colorAtCenterPixel = bitmap.getPixel(centerX, centerY);
diff --git a/v7/appcompat/tests/src/android/support/v7/testutils/TestUtilsMatchers.java b/v7/appcompat/tests/src/android/support/v7/testutils/TestUtilsMatchers.java
index ac10d3b..3e092c4 100644
--- a/v7/appcompat/tests/src/android/support/v7/testutils/TestUtilsMatchers.java
+++ b/v7/appcompat/tests/src/android/support/v7/testutils/TestUtilsMatchers.java
@@ -81,6 +81,14 @@
      * with the specific color.
      */
     public static Matcher isBackground(@ColorInt final int color) {
+        return isBackground(color, false);
+    }
+
+    /**
+     * Returns a matcher that matches <code>View</code>s which have background flat-filled
+     * with the specific color.
+     */
+    public static Matcher isBackground(@ColorInt final int color, final boolean onlyTestCenter) {
         return new BoundedMatcher<View, View>(View.class) {
             private String failedComparisonDescription;
 
@@ -97,10 +105,14 @@
                 if (drawable == null) {
                     return false;
                 }
-
                 try {
-                    TestUtils.assertAllPixelsOfColor("", drawable, view.getWidth(),
-                            view.getHeight(), false, color, 0, true);
+                    if (onlyTestCenter) {
+                        TestUtils.assertCenterPixelOfColor("", drawable, view.getWidth(),
+                                view.getHeight(), false, color, 0, true);
+                    } else {
+                        TestUtils.assertAllPixelsOfColor("", drawable, view.getWidth(),
+                                view.getHeight(), false, color, 0, true);
+                    }
                     // If we are here, the color comparison has passed.
                     failedComparisonDescription = null;
                     return true;
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 e721ebd..dca0f2a 100644
--- a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatBaseViewTest.java
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatBaseViewTest.java
@@ -16,7 +16,15 @@
 package android.support.v7.widget;
 
 import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
 import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static android.support.v7.testutils.AppCompatTintableViewActions.setBackgroundResource;
+import static android.support.v7.testutils.AppCompatTintableViewActions.setBackgroundTintList;
+import static android.support.v7.testutils.AppCompatTintableViewActions.setBackgroundTintMode;
+import static android.support.v7.testutils.AppCompatTintableViewActions.setEnabled;
+import static android.support.v7.testutils.TestUtilsActions.setBackgroundTintListViewCompat;
+import static android.support.v7.testutils.TestUtilsActions.setBackgroundTintModeViewCompat;
+import static android.support.v7.testutils.TestUtilsMatchers.isBackground;
 
 import static org.junit.Assert.assertNull;
 
@@ -27,7 +35,6 @@
 import android.support.annotation.ColorInt;
 import android.support.annotation.IdRes;
 import android.support.annotation.NonNull;
-import android.support.test.filters.SmallTest;
 import android.support.v4.content.res.ResourcesCompat;
 import android.support.v4.graphics.ColorUtils;
 import android.support.v7.app.BaseInstrumentationTestCase;
@@ -35,7 +42,7 @@
 import android.support.v7.testutils.AppCompatTintableViewActions;
 import android.support.v7.testutils.BaseTestActivity;
 import android.support.v7.testutils.TestUtils;
-import android.support.v7.testutils.TestUtilsActions;
+import android.test.suitebuilder.annotation.SmallTest;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -102,11 +109,11 @@
         assertNull("No background after XML loading", view.getBackground());
 
         // Disable the view and check that the background is still null.
-        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
+        onView(withId(viewId)).perform(setEnabled(false));
         assertNull("No background after disabling", view.getBackground());
 
         // Enable the view and check that the background is still null.
-        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
+        onView(withId(viewId)).perform(setEnabled(true));
         assertNull("No background after re-enabling", view.getBackground());
 
         // Load a new color state list, set it on the view and check that the background
@@ -114,14 +121,14 @@
         final ColorStateList sandColor = ResourcesCompat.getColorStateList(
                 mResources, R.color.color_state_list_sand, null);
         onView(withId(viewId)).perform(
-                AppCompatTintableViewActions.setBackgroundTintList(sandColor));
+                setBackgroundTintList(sandColor));
 
         // Disable the view and check that the background is still null.
-        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
+        onView(withId(viewId)).perform(setEnabled(false));
         assertNull("No background after disabling", view.getBackground());
 
         // Enable the view and check that the background is still null.
-        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
+        onView(withId(viewId)).perform(setEnabled(true));
         assertNull("No background after re-enabling", view.getBackground());
     }
 
@@ -146,26 +153,25 @@
         assertNull("No background after XML loading", view.getBackground());
 
         // Disable the view and check that the background is still null.
-        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
+        onView(withId(viewId)).perform(setEnabled(false));
         assertNull("No background after disabling", view.getBackground());
 
         // Enable the view and check that the background is still null.
-        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
+        onView(withId(viewId)).perform(setEnabled(true));
         assertNull("No background after re-enabling", view.getBackground());
 
         // Load a new color state list, set it on the view and check that the background
         // is still null.
         final ColorStateList lilacColor = ResourcesCompat.getColorStateList(
                 mResources, R.color.color_state_list_lilac, null);
-        onView(withId(viewId)).perform(
-                TestUtilsActions.setBackgroundTintListViewCompat(lilacColor));
+        onView(withId(viewId)).perform(setBackgroundTintListViewCompat(lilacColor));
 
         // Disable the view and check that the background is still null.
-        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
+        onView(withId(viewId)).perform(setEnabled(false));
         assertNull("No background after disabling", view.getBackground());
 
         // Enable the view and check that the background is still null.
-        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
+        onView(withId(viewId)).perform(setEnabled(true));
         assertNull("No background after re-enabling", view.getBackground());
     }
 
@@ -199,13 +205,13 @@
 
         // Disable the view and check that the background has switched to the matching entry
         // in the default color state list.
-        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
+        onView(withId(viewId)).perform(setEnabled(false));
         verifyBackgroundIsColoredAs("Default lilac tinting in disabled state", view,
                 lilacDisabled, 0);
 
         // Enable the view and check that the background has switched to the matching entry
         // in the default color state list.
-        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
+        onView(withId(viewId)).perform(setEnabled(true));
         verifyBackgroundIsColoredAs("Default lilac tinting in re-enabled state", view,
                 lilacDefault, 0);
 
@@ -214,19 +220,19 @@
         final ColorStateList sandColor = ResourcesCompat.getColorStateList(
                 mResources, R.color.color_state_list_sand, null);
         onView(withId(viewId)).perform(
-                AppCompatTintableViewActions.setBackgroundTintList(sandColor));
+                setBackgroundTintList(sandColor));
         verifyBackgroundIsColoredAs("New sand tinting in enabled state", view,
                 sandDefault, 0);
 
         // Disable the view and check that the background has switched to the matching entry
         // in the newly set color state list.
-        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
+        onView(withId(viewId)).perform(setEnabled(false));
         verifyBackgroundIsColoredAs("New sand tinting in disabled state", view,
                 sandDisabled, 0);
 
         // Enable the view and check that the background has switched to the matching entry
         // in the newly set color state list.
-        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
+        onView(withId(viewId)).perform(setEnabled(true));
         verifyBackgroundIsColoredAs("New sand tinting in re-enabled state", view,
                 sandDefault, 0);
 
@@ -234,20 +240,19 @@
         // switched to the matching entry in newly set color state list.
         final ColorStateList oceanColor = ResourcesCompat.getColorStateList(
                 mResources, R.color.color_state_list_ocean, null);
-        onView(withId(viewId)).perform(
-                AppCompatTintableViewActions.setBackgroundTintList(oceanColor));
+        onView(withId(viewId)).perform(setBackgroundTintList(oceanColor));
         verifyBackgroundIsColoredAs("New ocean tinting in enabled state", view,
                 oceanDefault, 0);
 
         // Disable the view and check that the background has switched to the matching entry
         // in the newly set color state list.
-        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
+        onView(withId(viewId)).perform(setEnabled(false));
         verifyBackgroundIsColoredAs("New ocean tinting in disabled state", view,
                 oceanDisabled, 0);
 
         // Enable the view and check that the background has switched to the matching entry
         // in the newly set color state list.
-        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
+        onView(withId(viewId)).perform(setEnabled(true));
         verifyBackgroundIsColoredAs("New ocean tinting in re-enabled state", view,
                 oceanDefault, 0);
     }
@@ -282,13 +287,13 @@
 
         // Disable the view and check that the background has switched to the matching entry
         // in the default color state list.
-        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
+        onView(withId(viewId)).perform(setEnabled(false));
         verifyBackgroundIsColoredAs("Default lilac tinting in disabled state", view,
                 lilacDisabled, 0);
 
         // Enable the view and check that the background has switched to the matching entry
         // in the default color state list.
-        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
+        onView(withId(viewId)).perform(setEnabled(true));
         verifyBackgroundIsColoredAs("Default lilac tinting in re-enabled state", view,
                 lilacDefault, 0);
 
@@ -296,20 +301,19 @@
         // switched to the matching entry in newly set color state list.
         final ColorStateList sandColor = ResourcesCompat.getColorStateList(
                 mResources, R.color.color_state_list_sand, null);
-        onView(withId(viewId)).perform(
-                TestUtilsActions.setBackgroundTintListViewCompat(sandColor));
+        onView(withId(viewId)).perform(setBackgroundTintListViewCompat(sandColor));
         verifyBackgroundIsColoredAs("New sand tinting in enabled state", view,
                 sandDefault, 0);
 
         // Disable the view and check that the background has switched to the matching entry
         // in the newly set color state list.
-        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
+        onView(withId(viewId)).perform(setEnabled(false));
         verifyBackgroundIsColoredAs("New sand tinting in disabled state", view,
                 sandDisabled, 0);
 
         // Enable the view and check that the background has switched to the matching entry
         // in the newly set color state list.
-        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
+        onView(withId(viewId)).perform(setEnabled(true));
         verifyBackgroundIsColoredAs("New sand tinting in re-enabled state", view,
                 sandDefault, 0);
 
@@ -318,19 +322,19 @@
         final ColorStateList oceanColor = ResourcesCompat.getColorStateList(
                 mResources, R.color.color_state_list_ocean, null);
         onView(withId(viewId)).perform(
-                TestUtilsActions.setBackgroundTintListViewCompat(oceanColor));
+                setBackgroundTintListViewCompat(oceanColor));
         verifyBackgroundIsColoredAs("New ocean tinting in enabled state", view,
                 oceanDefault, 0);
 
         // Disable the view and check that the background has switched to the matching entry
         // in the newly set color state list.
-        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
+        onView(withId(viewId)).perform(setEnabled(false));
         verifyBackgroundIsColoredAs("New ocean tinting in disabled state", view,
                 oceanDisabled, 0);
 
         // Enable the view and check that the background has switched to the matching entry
         // in the newly set color state list.
-        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
+        onView(withId(viewId)).perform(setEnabled(true));
         verifyBackgroundIsColoredAs("New ocean tinting in re-enabled state", view,
                 oceanDefault, 0);
     }
@@ -367,21 +371,19 @@
         final int allowedComponentVariance = 2;
 
         // Set src_in tint mode on our view
-        onView(withId(viewId)).perform(
-                AppCompatTintableViewActions.setBackgroundTintMode(PorterDuff.Mode.SRC_IN));
+        onView(withId(viewId)).perform(setBackgroundTintMode(PorterDuff.Mode.SRC_IN));
 
         // 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(
                 mResources, R.color.color_state_list_emerald_translucent, null);
-        onView(withId(viewId)).perform(
-                AppCompatTintableViewActions.setBackgroundTintList(emeraldColor));
+        onView(withId(viewId)).perform(setBackgroundTintList(emeraldColor));
         verifyBackgroundIsColoredAs("New emerald tinting in enabled state under src_in", view,
                 emeraldDefault, allowedComponentVariance);
 
         // Disable the view and check that the background has switched to the matching entry
         // in the newly set color state list.
-        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
+        onView(withId(viewId)).perform(setEnabled(false));
         verifyBackgroundIsColoredAs("New emerald tinting in disabled state under src_in", view,
                 emeraldDisabled, allowedComponentVariance);
 
@@ -389,19 +391,18 @@
         // translucent colors, we expect the actual background of the view to be different under
         // this new mode (unlike src_in and src_over that behave identically when the destination is
         // a fully filled rectangle and the source is an opaque color).
-        onView(withId(viewId)).perform(
-                AppCompatTintableViewActions.setBackgroundTintMode(PorterDuff.Mode.SRC_OVER));
+        onView(withId(viewId)).perform(setBackgroundTintMode(PorterDuff.Mode.SRC_OVER));
 
         // Enable the view and check that the background has switched to the matching entry
         // in the color state list.
-        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
+        onView(withId(viewId)).perform(setEnabled(true));
         verifyBackgroundIsColoredAs("New emerald tinting in enabled state under src_over", view,
                 ColorUtils.compositeColors(emeraldDefault, backgroundColor),
                 allowedComponentVariance);
 
         // Disable the view and check that the background has switched to the matching entry
         // in the newly set color state list.
-        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
+        onView(withId(viewId)).perform(setEnabled(false));
         verifyBackgroundIsColoredAs("New emerald tinting in disabled state under src_over",
                 view, ColorUtils.compositeColors(emeraldDisabled, backgroundColor),
                 allowedComponentVariance);
@@ -439,21 +440,19 @@
         final int allowedComponentVariance = 2;
 
         // Set src_in tint mode on our view
-        onView(withId(viewId)).perform(
-                TestUtilsActions.setBackgroundTintModeViewCompat(PorterDuff.Mode.SRC_IN));
+        onView(withId(viewId)).perform(setBackgroundTintModeViewCompat(PorterDuff.Mode.SRC_IN));
 
         // 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(
                 mResources, R.color.color_state_list_emerald_translucent, null);
-        onView(withId(viewId)).perform(
-                TestUtilsActions.setBackgroundTintListViewCompat(emeraldColor));
+        onView(withId(viewId)).perform(setBackgroundTintListViewCompat(emeraldColor));
         verifyBackgroundIsColoredAs("New emerald tinting in enabled state under src_in", view,
                 emeraldDefault, allowedComponentVariance);
 
         // Disable the view and check that the background has switched to the matching entry
         // in the newly set color state list.
-        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
+        onView(withId(viewId)).perform(setEnabled(false));
         verifyBackgroundIsColoredAs("New emerald tinting in disabled state under src_in", view,
                 emeraldDisabled, allowedComponentVariance);
 
@@ -461,19 +460,18 @@
         // translucent colors, we expect the actual background of the view to be different under
         // this new mode (unlike src_in and src_over that behave identically when the destination is
         // a fully filled rectangle and the source is an opaque color).
-        onView(withId(viewId)).perform(
-                TestUtilsActions.setBackgroundTintModeViewCompat(PorterDuff.Mode.SRC_OVER));
+        onView(withId(viewId)).perform(setBackgroundTintModeViewCompat(PorterDuff.Mode.SRC_OVER));
 
         // Enable the view and check that the background has switched to the matching entry
         // in the color state list.
-        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
+        onView(withId(viewId)).perform(setEnabled(true));
         verifyBackgroundIsColoredAs("New emerald tinting in enabled state under src_over", view,
                 ColorUtils.compositeColors(emeraldDefault, backgroundColor),
                 allowedComponentVariance);
 
         // Disable the view and check that the background has switched to the matching entry
         // in the newly set color state list.
-        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
+        onView(withId(viewId)).perform(setEnabled(false));
         verifyBackgroundIsColoredAs("New emerald tinting in disabled state under src_over",
                 view, ColorUtils.compositeColors(emeraldDisabled, backgroundColor),
                 allowedComponentVariance);
@@ -499,8 +497,7 @@
         }
 
         // Set background on our view
-        onView(withId(viewId)).perform(AppCompatTintableViewActions.setBackgroundDrawable(
-                ResourcesCompat.getDrawable(mResources, R.drawable.test_background_green, null)));
+        onView(withId(viewId)).perform(setBackgroundResource(R.drawable.test_background_green));
 
         // Test the default state for tinting set up in the layout XML file.
         verifyBackgroundIsColoredAs("Default lilac tinting in enabled state on green background",
@@ -508,13 +505,13 @@
 
         // Disable the view and check that the background has switched to the matching entry
         // in the default color state list.
-        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
+        onView(withId(viewId)).perform(setEnabled(false));
         verifyBackgroundIsColoredAs("Default lilac tinting in disabled state on green background",
                 view, lilacDisabled, 0);
 
         // Enable the view and check that the background has switched to the matching entry
         // in the default color state list.
-        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
+        onView(withId(viewId)).perform(setEnabled(true));
         verifyBackgroundIsColoredAs("Default lilac tinting in re-enabled state on green background",
                 view, lilacDefault, 0);
 
@@ -528,13 +525,13 @@
 
         // Disable the view and check that the background has switched to the matching entry
         // in the default color state list.
-        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
+        onView(withId(viewId)).perform(setEnabled(false));
         verifyBackgroundIsColoredAs("Default lilac tinting in disabled state on red background",
                 view, lilacDisabled, 0);
 
         // Enable the view and check that the background has switched to the matching entry
         // in the default color state list.
-        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
+        onView(withId(viewId)).perform(setEnabled(true));
         verifyBackgroundIsColoredAs("Default lilac tinting in re-enabled state on red background",
                 view, lilacDefault, 0);
     }
@@ -568,17 +565,15 @@
         // translucent colors, we expect the actual background of the view to be different under
         // this new mode (unlike src_in and src_over that behave identically when the destination is
         // a fully filled rectangle and the source is an opaque color).
-        onView(withId(viewId)).perform(
-                AppCompatTintableViewActions.setBackgroundTintMode(PorterDuff.Mode.SRC_OVER));
+        onView(withId(viewId)).perform(setBackgroundTintMode(PorterDuff.Mode.SRC_OVER));
         // Load and set a translucent color state list as the background tint list
         final ColorStateList emeraldColor = ResourcesCompat.getColorStateList(
                 mResources, R.color.color_state_list_emerald_translucent, null);
         onView(withId(viewId)).perform(
-                AppCompatTintableViewActions.setBackgroundTintList(emeraldColor));
+                setBackgroundTintList(emeraldColor));
 
         // Set background on our view
-        onView(withId(viewId)).perform(AppCompatTintableViewActions.setBackgroundDrawable(
-                ResourcesCompat.getDrawable(mResources, R.drawable.test_background_green, null)));
+        onView(withId(viewId)).perform(setBackgroundResource(R.drawable.test_background_green));
 
         // 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
@@ -594,14 +589,14 @@
 
         // Disable the view and check that the background has switched to the matching entry
         // in the default color state list.
-        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
+        onView(withId(viewId)).perform(setEnabled(false));
         verifyBackgroundIsColoredAs("Emerald tinting in disabled state on green background",
                 view, ColorUtils.compositeColors(emeraldDisabled, backgroundColorGreen),
                 allowedComponentVariance);
 
         // Enable the view and check that the background has switched to the matching entry
         // in the default color state list.
-        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
+        onView(withId(viewId)).perform(setEnabled(true));
         verifyBackgroundIsColoredAs("Emerald tinting in re-enabled state on green background",
                 view, ColorUtils.compositeColors(emeraldDefault, backgroundColorGreen),
                 allowedComponentVariance);
@@ -617,16 +612,39 @@
 
         // Disable the view and check that the background has switched to the matching entry
         // in our current color state list.
-        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
+        onView(withId(viewId)).perform(setEnabled(false));
         verifyBackgroundIsColoredAs("Emerald tinting in disabled state on red background",
                 view, ColorUtils.compositeColors(emeraldDisabled, backgroundColorRed),
                 allowedComponentVariance);
 
         // Enable the view and check that the background has switched to the matching entry
         // in our current color state list.
-        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
+        onView(withId(viewId)).perform(setEnabled(true));
         verifyBackgroundIsColoredAs("Emerald tinting in re-enabled state on red background",
                 view, ColorUtils.compositeColors(emeraldDefault, backgroundColorRed),
                 allowedComponentVariance);
     }
+
+    protected void testUntintedBackgroundTintingViewCompatAcrossStateChange(@IdRes int viewId) {
+        final T view = (T) mContainer.findViewById(viewId);
+
+        final @ColorInt int oceanDefault = ResourcesCompat.getColor(
+                mResources, R.color.ocean_default, null);
+        final @ColorInt int oceanDisabled = ResourcesCompat.getColor(
+                mResources, R.color.ocean_disabled, null);
+
+        final ColorStateList oceanColor = ResourcesCompat.getColorStateList(
+                mResources, R.color.color_state_list_ocean, null);
+        onView(withId(viewId)).perform(setBackgroundTintListViewCompat(oceanColor));
+
+        // Disable the view and check that the background has switched to the matching entry
+        // in the newly set color state list.
+        onView(withId(viewId)).perform(setEnabled(false))
+                .check(matches(isBackground(oceanDisabled, true)));
+
+        // Enable the view and check that the background has switched to the matching entry
+        // in the newly set color state list.
+        onView(withId(viewId)).perform(setEnabled(true))
+                .check(matches(isBackground(oceanDefault, true)));
+    }
 }
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatButtonTest.java b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatButtonTest.java
index 1ea0468..2e5a952 100644
--- a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatButtonTest.java
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatButtonTest.java
@@ -21,6 +21,7 @@
 
 import static org.junit.Assert.assertEquals;
 
+import android.support.test.filters.SdkSuppress;
 import android.support.test.filters.SmallTest;
 import android.support.v7.appcompat.test.R;
 
@@ -81,4 +82,13 @@
 
         assertEquals("Button is not in all caps", text, button.getLayout().getText());
     }
+
+    /**
+     * Currently only runs on API 22+ due to http://b.android.com/221469
+     */
+    @Test
+    @SdkSuppress(minSdkVersion = 22)
+    public void testBackgroundTintListOnColoredButton() {
+        testUntintedBackgroundTintingViewCompatAcrossStateChange(R.id.button_colored_untinted);
+    }
 }