Extending background tinting tests to AppCompatImageView

Extract all the tint-related logic to a new generic class and
have AppCompatTextView / AppCompatImageView (and more in the next
CLs) inherit all that logic.

Then specific subclasses can add tests that are specific to one
component - such as all-caps in AppCompatTextView.

Change-Id: Ic4c00983feff58c435033bdc0e3ce67ec774439a
diff --git a/v7/appcompat/tests/AndroidManifest.xml b/v7/appcompat/tests/AndroidManifest.xml
index 0813954..1c6a855 100644
--- a/v7/appcompat/tests/AndroidManifest.xml
+++ b/v7/appcompat/tests/AndroidManifest.xml
@@ -55,6 +55,11 @@
                 android:label="@string/app_compat_text_view_activity"
                 android:theme="@style/Theme.AppCompat.Light" />
 
+        <activity
+                android:name="android.support.v7.widget.AppCompatImageViewActivity"
+                android:label="@string/app_compat_image_view_activity"
+                android:theme="@style/Theme.AppCompat.Light" />
+
     </application>
 
     <instrumentation
diff --git a/v7/appcompat/tests/res/layout/appcompat_imageview_activity.xml b/v7/appcompat/tests/res/layout/appcompat_imageview_activity.xml
new file mode 100644
index 0000000..fed24b1
--- /dev/null
+++ b/v7/appcompat/tests/res/layout/appcompat_imageview_activity.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<ScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <android.support.v7.widget.AppCompatImageView
+            android:id="@+id/view_tinted_no_background"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:src="@drawable/test_drawable_blue"
+            app:backgroundTint="@color/color_state_list_lilac"
+            app:backgroundTintMode="src_in" />
+
+        <android.support.v7.widget.AppCompatImageView
+            android:id="@+id/view_tinted_background"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:src="@drawable/test_drawable_blue"
+            android:background="@drawable/test_drawable"
+            app:backgroundTint="@color/color_state_list_lilac"
+            app:backgroundTintMode="src_in" />
+
+        <android.support.v7.widget.AppCompatImageView
+            android:id="@+id/view_untinted_no_background"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:src="@drawable/test_drawable_blue" />
+
+        <android.support.v7.widget.AppCompatImageView
+            android:id="@+id/view_untinted_background"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:src="@drawable/test_drawable_blue"
+            android:background="@drawable/test_background_green" />
+    </LinearLayout>
+
+</ScrollView>
diff --git a/v7/appcompat/tests/res/layout/appcompat_textview_activity.xml b/v7/appcompat/tests/res/layout/appcompat_textview_activity.xml
index a7b33e6..e093de9 100644
--- a/v7/appcompat/tests/res/layout/appcompat_textview_activity.xml
+++ b/v7/appcompat/tests/res/layout/appcompat_textview_activity.xml
@@ -40,7 +40,7 @@
             android:text="@string/sample_text2" />
 
         <android.support.v7.widget.AppCompatTextView
-            android:id="@+id/text_view_tinted_no_background"
+            android:id="@+id/view_tinted_no_background"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:text="@string/sample_text1"
@@ -48,7 +48,7 @@
             app:backgroundTintMode="src_in" />
 
         <android.support.v7.widget.AppCompatTextView
-            android:id="@+id/text_view_tinted_background"
+            android:id="@+id/view_tinted_background"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:text="@string/sample_text2"
@@ -57,13 +57,13 @@
             app:backgroundTintMode="src_in" />
 
         <android.support.v7.widget.AppCompatTextView
-            android:id="@+id/text_view_untinted_no_background"
+            android:id="@+id/view_untinted_no_background"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:text="@string/sample_text2" />
 
         <android.support.v7.widget.AppCompatTextView
-            android:id="@+id/text_view_untinted_background"
+            android:id="@+id/view_untinted_background"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:text="@string/sample_text2"
diff --git a/v7/appcompat/tests/res/values/strings.xml b/v7/appcompat/tests/res/values/strings.xml
index bcbe38a..b7347a2 100644
--- a/v7/appcompat/tests/res/values/strings.xml
+++ b/v7/appcompat/tests/res/values/strings.xml
@@ -44,4 +44,5 @@
     <string name="app_compat_text_view_activity">AppCompat text view</string>
     <string name="sample_text1">Sample text 1</string>
     <string name="sample_text2">Sample text 2</string>
+    <string name="app_compat_image_view_activity">AppCompat image view</string>
 </resources>
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatBaseViewTest.java b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatBaseViewTest.java
new file mode 100644
index 0000000..b517fc3
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatBaseViewTest.java
@@ -0,0 +1,402 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.widget;
+
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.ColorInt;
+import android.support.annotation.IdRes;
+import android.support.annotation.NonNull;
+import android.support.v4.content.res.ResourcesCompat;
+import android.support.v4.graphics.ColorUtils;
+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 static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+
+/**
+ * Base class for testing custom view extensions in appcompat-v7 that implement the
+ * <code>TintableBackgroundView</code> interface. Extensions of this class run all tests
+ * from here and add test cases specific to the functionality they add to the relevant
+ * 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> {
+    protected ViewGroup mContainer;
+
+    public AppCompatBaseViewTest(Class clazz) {
+        super(clazz);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        final A activity = getActivity();
+        mContainer = (ViewGroup) activity.findViewById(R.id.container);
+    }
+
+    private void verifyBackgroundIsColoredAs(String description, @NonNull View view,
+            @ColorInt int color, int allowedComponentVariance) {
+        Drawable background = view.getBackground();
+        TestUtils.assertAllPixelsOfColor(description,
+                background, view.getWidth(), view.getHeight(), true,
+                color, allowedComponentVariance, false);
+    }
+
+    /**
+     * This method tests that background tinting is not applied when the
+     * tintable view has no background.
+     */
+    @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
+        // is null. This is because the matching child in the activity doesn't define any
+        // background on itself, and there is nothing to tint.
+
+        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));
+        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));
+        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 sandColor = ResourcesCompat.getColorStateList(
+                res, R.color.color_state_list_sand, null);
+        onView(withId(viewId)).perform(
+                AppCompatTintableViewActions.setBackgroundTintList(sandColor));
+
+        // Disable the view and check that the background is still null.
+        onView(withId(viewId)).perform(AppCompatTintableViewActions.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));
+        assertNull("No background after re-enabling", view.getBackground());
+    }
+
+    /**
+     * This method tests that background tinting is applied to tintable view
+     * in enabled and disabled state across a number of <code>ColorStateList</code>s set as
+     * background tint lists on the same background.
+     */
+    @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);
+
+        // Test the default state for tinting set up in the layout XML file.
+        verifyBackgroundIsColoredAs("Default lilac tinting in enabled state", view,
+                lilacDefault, 0);
+
+        // 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));
+        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));
+        verifyBackgroundIsColoredAs("Default lilac tinting in re-enabled state", view,
+                lilacDefault, 0);
+
+        // 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);
+        onView(withId(viewId)).perform(
+                AppCompatTintableViewActions.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));
+        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));
+        verifyBackgroundIsColoredAs("New sand tinting in re-enabled state", view,
+                sandDefault, 0);
+
+        // 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);
+        onView(withId(viewId)).perform(
+                AppCompatTintableViewActions.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));
+        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));
+        verifyBackgroundIsColoredAs("New ocean tinting in re-enabled state", view,
+                oceanDefault, 0);
+    }
+
+    /**
+     * This method tests that background tinting applied to tintable view
+     * in enabled and disabled state across the same background respects the currently set
+     * background tinting mode.
+     */
+    @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);
+        // 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);
+
+        // Test the default state for tinting set up in the layout XML file.
+        verifyBackgroundIsColoredAs("Default no tinting in enabled state", view,
+                backgroundColor, 0);
+
+        // 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
+        // in the color state list and off-by-one discrepancies of SRC_OVER when it's compositing
+        // translucent color on top of solid fill color. This is where the allowed variance
+        // value of 2 comes from - one for compositing and one for color translucency.
+        final int allowedComponentVariance = 2;
+
+        // Set src_in tint mode on our view
+        onView(withId(viewId)).perform(
+                AppCompatTintableViewActions.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(
+                res, 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,
+                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));
+        verifyBackgroundIsColoredAs("New emerald tinting in disabled state under src_in", view,
+                emeraldDisabled, allowedComponentVariance);
+
+        // Set src_over tint mode on our view. As the currently set tint list is using
+        // translucent colors, we expect the actual background of the view to be different under
+        // 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));
+
+        // 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));
+        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));
+        verifyBackgroundIsColoredAs("New emerald tinting in disabled state under src_over",
+                view, ColorUtils.compositeColors(emeraldDisabled, backgroundColor),
+                allowedComponentVariance);
+    }
+
+    /**
+     * This method tests that opaque background tinting applied to tintable view
+     * is applied correctly after changing the background itself of the view.
+     */
+    @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);
+
+        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)));
+
+        // Test the default state for tinting set up in the layout XML file.
+        verifyBackgroundIsColoredAs("Default lilac tinting in enabled state on green background",
+                view, lilacDefault, 0);
+
+        // 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));
+        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));
+        verifyBackgroundIsColoredAs("Default lilac tinting in re-enabled state on green background",
+                view, lilacDefault, 0);
+
+        // Set a different background on our view based on resource ID
+        onView(withId(viewId)).perform(AppCompatTintableViewActions.setBackgroundResource(
+                R.drawable.test_background_red));
+
+        // Test the default state for tinting set up in the layout XML file.
+        verifyBackgroundIsColoredAs("Default lilac tinting in enabled state on red background",
+                view, lilacDefault, 0);
+
+        // 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));
+        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));
+        verifyBackgroundIsColoredAs("Default lilac tinting in re-enabled state on red background",
+                view, lilacDefault, 0);
+    }
+
+    /**
+     * This method tests that translucent background tinting applied to tintable view
+     * is applied correctly after changing the background itself of the view.
+     */
+    @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);
+        // 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);
+
+        assertNull("No background after XML loading", view.getBackground());
+
+        // Set src_over tint mode on our view. As the currently set tint list is using
+        // translucent colors, we expect the actual background of the view to be different under
+        // 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));
+        // 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);
+        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)));
+
+        // 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
+        // in the color state list and off-by-one discrepancies of SRC_OVER when it's compositing
+        // translucent color on top of solid fill color. This is where the allowed variance
+        // value of 2 comes from - one for compositing and one for color translucency.
+        final int allowedComponentVariance = 2;
+
+        // Test the default state for tinting set up with the just loaded tint list.
+        verifyBackgroundIsColoredAs("Emerald tinting in enabled state on green background",
+                view, ColorUtils.compositeColors(emeraldDefault, backgroundColorGreen),
+                allowedComponentVariance);
+
+        // 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));
+        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));
+        verifyBackgroundIsColoredAs("Emerald tinting in re-enabled state on green background",
+                view, ColorUtils.compositeColors(emeraldDefault, backgroundColorGreen),
+                allowedComponentVariance);
+
+        // Set a different background on our view based on resource ID
+        onView(withId(viewId)).perform(AppCompatTintableViewActions.setBackgroundResource(
+                R.drawable.test_background_red));
+
+        // Test the default state for tinting the new background with the same color state list
+        verifyBackgroundIsColoredAs("Emerald tinting in enabled state on red background",
+                view, ColorUtils.compositeColors(emeraldDefault, backgroundColorRed),
+                allowedComponentVariance);
+
+        // 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));
+        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));
+        verifyBackgroundIsColoredAs("Emerald tinting in re-enabled state on red background",
+                view, ColorUtils.compositeColors(emeraldDefault, backgroundColorRed),
+                allowedComponentVariance);
+    }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatImageViewActivity.java b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatImageViewActivity.java
new file mode 100644
index 0000000..f41f7a3
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatImageViewActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.widget;
+
+import android.support.v7.appcompat.test.R;
+import android.support.v7.testutils.BaseTestActivity;
+
+public class AppCompatImageViewActivity extends BaseTestActivity {
+    @Override
+    protected int getContentViewLayoutResId() {
+        return R.layout.appcompat_imageview_activity;
+    }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatImageViewTest.java b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatImageViewTest.java
new file mode 100644
index 0000000..d2abe95
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatImageViewTest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.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.
+ */
+public class AppCompatImageViewTest
+        extends AppCompatBaseViewTest<AppCompatImageViewActivity, AppCompatImageView> {
+    public AppCompatImageViewTest() {
+        super(AppCompatImageViewActivity.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 d31ce90..6448e7d 100644
--- a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewTest.java
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewTest.java
@@ -15,42 +15,24 @@
  */
 package android.support.v7.widget;
 
-import android.content.res.ColorStateList;
 import android.content.res.Resources;
-import android.graphics.PorterDuff;
-import android.graphics.drawable.Drawable;
-import android.support.annotation.ColorInt;
-import android.support.annotation.IdRes;
-import android.support.annotation.NonNull;
-import android.support.v4.content.res.ResourcesCompat;
-import android.support.v4.graphics.ColorUtils;
 import android.support.v7.appcompat.test.R;
 import android.support.v7.testutils.AppCompatTextViewActions;
-import android.support.v7.testutils.AppCompatTintableViewActions;
-import android.support.v7.testutils.TestUtils;
-import android.test.ActivityInstrumentationTestCase2;
 import android.test.suitebuilder.annotation.SmallTest;
-import android.view.ViewGroup;
 
 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>AppCompatTextView</code> class.
+ */
 public class AppCompatTextViewTest
-        extends ActivityInstrumentationTestCase2<AppCompatTextViewActivity> {
-    private ViewGroup mContainer;
-
+        extends AppCompatBaseViewTest<AppCompatTextViewActivity, AppCompatTextView> {
     public AppCompatTextViewTest() {
         super(AppCompatTextViewActivity.class);
     }
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        final AppCompatTextViewActivity activity = getActivity();
-        mContainer = (ViewGroup) activity.findViewById(R.id.container);
-    }
-
     @SmallTest
     public void testAllCaps() throws Throwable {
         final Resources res = getActivity().getResources();
@@ -83,352 +65,4 @@
         assertEquals("Text view is in all caps on", text2.toUpperCase(),
                 textView2.getLayout().getText());
     }
-
-    private void verifyBackgroundIsColoredAs(String description,
-            @NonNull AppCompatTextView textView, @ColorInt int color,
-            int allowedComponentVariance) {
-        Drawable background = textView.getBackground();
-        TestUtils.assertAllPixelsOfColor(description,
-                background, textView.getWidth(), textView.getHeight(), true,
-                color, allowedComponentVariance, false);
-    }
-
-    /**
-     * This method tests that background tinting is not applied when the
-     * <code>AppCompatTextView</code> has no background.
-     */
-    @SmallTest
-    public void testBackgroundTintingWithNoBackground() {
-        final @IdRes int textViewId = R.id.text_view_tinted_no_background;
-        final AppCompatTextView textView =
-                (AppCompatTextView) mContainer.findViewById(textViewId);
-
-        // Note that all the asserts in this test check that the AppCompatTextView background
-        // is null. This is because the matching child in the activity doesn't define any
-        // background on itself, and there is nothing to tint.
-
-        assertNull("No background after XML loading", textView.getBackground());
-
-        // Disable the text view and check that the background is still null.
-        onView(withId(textViewId)).perform(
-                AppCompatTintableViewActions.setEnabled(false));
-        assertNull("No background after disabling", textView.getBackground());
-
-        // Enable the text view and check that the background is still null.
-        onView(withId(textViewId)).perform(
-                AppCompatTintableViewActions.setEnabled(true));
-        assertNull("No background after re-enabling", textView.getBackground());
-
-        // Load a new color state list, set it on the text view and check that the background
-        // is still null.
-        final ColorStateList sandColor = ResourcesCompat.getColorStateList(
-                getActivity().getResources(), R.color.color_state_list_sand, null);
-        onView(withId(textViewId)).perform(
-                AppCompatTintableViewActions.setBackgroundTintList(sandColor));
-
-        // Disable the text view and check that the background is still null.
-        onView(withId(textViewId)).perform(
-                AppCompatTintableViewActions.setEnabled(false));
-        assertNull("No background after disabling", textView.getBackground());
-
-        // Enable the text view and check that the background is still null.
-        onView(withId(textViewId)).perform(
-                AppCompatTintableViewActions.setEnabled(true));
-        assertNull("No background after re-enabling", textView.getBackground());
-    }
-
-    /**
-     * This method tests that background tinting is applied to <code>AppCompatTextView</code>
-     * in enabled and disabled state across a number of <code>ColorStateList</code>s set as
-     * background tint lists on the same background.
-     */
-    @SmallTest
-    public void testBackgroundTintingAcrossStateChange() {
-        final @IdRes int textViewId = R.id.text_view_tinted_background;
-        final Resources res = getActivity().getResources();
-
-        @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 AppCompatTextView textView =
-                (AppCompatTextView) mContainer.findViewById(textViewId);
-
-        // Test the default state for tinting set up in the layout XML file.
-        verifyBackgroundIsColoredAs("Default lilac tinting in enabled state", textView,
-                lilacDefault, 0);
-
-        // Disable the text view and check that the background has switched to the matching entry
-        // in the default color state list.
-        onView(withId(textViewId)).perform(AppCompatTintableViewActions.setEnabled(false));
-        verifyBackgroundIsColoredAs("Default lilac tinting in disabled state", textView,
-                lilacDisabled, 0);
-
-        // Enable the text view and check that the background has switched to the matching entry
-        // in the default color state list.
-        onView(withId(textViewId)).perform(AppCompatTintableViewActions.setEnabled(true));
-        verifyBackgroundIsColoredAs("Default lilac tinting in re-enabled state", textView,
-                lilacDefault, 0);
-
-        // Load a new color state list, set it on the text 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);
-        onView(withId(textViewId)).perform(
-                AppCompatTintableViewActions.setBackgroundTintList(sandColor));
-        verifyBackgroundIsColoredAs("New sand tinting in enabled state", textView,
-                sandDefault, 0);
-
-        // Disable the text view and check that the background has switched to the matching entry
-        // in the newly set color state list.
-        onView(withId(textViewId)).perform(AppCompatTintableViewActions.setEnabled(false));
-        verifyBackgroundIsColoredAs("New sand tinting in disabled state", textView,
-                sandDisabled, 0);
-
-        // Enable the text view and check that the background has switched to the matching entry
-        // in the newly set color state list.
-        onView(withId(textViewId)).perform(AppCompatTintableViewActions.setEnabled(true));
-        verifyBackgroundIsColoredAs("New sand tinting in re-enabled state", textView,
-                sandDefault, 0);
-
-        // Load another color state list, set it on the text 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);
-        onView(withId(textViewId)).perform(
-                AppCompatTintableViewActions.setBackgroundTintList(oceanColor));
-        verifyBackgroundIsColoredAs("New ocean tinting in enabled state", textView,
-                oceanDefault, 0);
-
-        // Disable the text view and check that the background has switched to the matching entry
-        // in the newly set color state list.
-        onView(withId(textViewId)).perform(AppCompatTintableViewActions.setEnabled(false));
-        verifyBackgroundIsColoredAs("New ocean tinting in disabled state", textView,
-                oceanDisabled, 0);
-
-        // Enable the text view and check that the background has switched to the matching entry
-        // in the newly set color state list.
-        onView(withId(textViewId)).perform(AppCompatTintableViewActions.setEnabled(true));
-        verifyBackgroundIsColoredAs("New ocean tinting in re-enabled state", textView,
-                oceanDefault, 0);
-    }
-
-    /**
-     * This method tests that background tinting applied to <code>AppCompatTextView</code>
-     * in enabled and disabled state across the same background respects the currently set
-     * background tinting mode.
-     */
-    @SmallTest
-    public void testBackgroundTintingAcrossModeChange() {
-        final @IdRes int textViewId = R.id.text_view_untinted_background;
-        final Resources res = getActivity().getResources();
-
-        @ColorInt int emeraldDefault = ResourcesCompat.getColor(
-                res, R.color.emerald_translucent_default, null);
-        @ColorInt int emeraldDisabled = ResourcesCompat.getColor(
-                res, R.color.emerald_translucent_disabled, null);
-        // This is the fill color of R.drawable.test_background_green set on our text view
-        // that we'll be testing in this method
-        @ColorInt int backgroundColor = ResourcesCompat.getColor(
-                res, R.color.test_green, null);
-
-        final AppCompatTextView textView =
-                (AppCompatTextView) mContainer.findViewById(textViewId);
-
-        // Test the default state for tinting set up in the layout XML file.
-        verifyBackgroundIsColoredAs("Default no tinting in enabled state", textView,
-                backgroundColor, 0);
-
-        // From this point on in this method we're allowing a margin of error in checking the
-        // color of the text view background. This is due to both translucent colors being used
-        // in the color state list and off-by-one discrepancies of SRC_OVER when it's compositing
-        // translucent color on top of solid fill color. This is where the allowed variance
-        // value of 2 comes from - one for compositing and one for color translucency.
-        final int allowedComponentVariance = 2;
-
-        // Set src_in tint mode on our text view
-        onView(withId(textViewId)).perform(
-                AppCompatTintableViewActions.setBackgroundTintMode(PorterDuff.Mode.SRC_IN));
-
-        // Load a new color state list, set it on the text 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);
-        onView(withId(textViewId)).perform(
-                AppCompatTintableViewActions.setBackgroundTintList(emeraldColor));
-        verifyBackgroundIsColoredAs("New emerald tinting in enabled state under src_in", textView,
-                emeraldDefault, allowedComponentVariance);
-
-        // Disable the text view and check that the background has switched to the matching entry
-        // in the newly set color state list.
-        onView(withId(textViewId)).perform(AppCompatTintableViewActions.setEnabled(false));
-        verifyBackgroundIsColoredAs("New emerald tinting in disabled state under src_in", textView,
-                emeraldDisabled, allowedComponentVariance);
-
-        // Set src_over tint mode on our text view. As the currently set tint list is using
-        // 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(textViewId)).perform(
-                AppCompatTintableViewActions.setBackgroundTintMode(PorterDuff.Mode.SRC_OVER));
-
-        // Enable the text view and check that the background has switched to the matching entry
-        // in the color state list.
-        onView(withId(textViewId)).perform(AppCompatTintableViewActions.setEnabled(true));
-        verifyBackgroundIsColoredAs("New emerald tinting in enabled state under src_over", textView,
-                ColorUtils.compositeColors(emeraldDefault, backgroundColor),
-                allowedComponentVariance);
-
-        // Disable the text view and check that the background has switched to the matching entry
-        // in the newly set color state list.
-        onView(withId(textViewId)).perform(AppCompatTintableViewActions.setEnabled(false));
-        verifyBackgroundIsColoredAs("New emerald tinting in disabled state under src_over",
-                textView, ColorUtils.compositeColors(emeraldDisabled, backgroundColor),
-                allowedComponentVariance);
-    }
-
-    /**
-     * This method tests that opaque background tinting applied to <code>AppCompatTextView</code>
-     * is applied correctly after changing the background itself of the view.
-     */
-    @SmallTest
-    public void testBackgroundOpaqueTintingAcrossBackgroundChange() {
-        final @IdRes int textViewId = R.id.text_view_tinted_no_background;
-        final Resources res = getActivity().getResources();
-
-        @ColorInt int lilacDefault = ResourcesCompat.getColor(res, R.color.lilac_default, null);
-        @ColorInt int lilacDisabled = ResourcesCompat.getColor(res, R.color.lilac_disabled, null);
-
-        final AppCompatTextView textView =
-                (AppCompatTextView) mContainer.findViewById(textViewId);
-
-        assertNull("No background after XML loading", textView.getBackground());
-
-        // Set background on our text view
-        onView(withId(textViewId)).perform(AppCompatTintableViewActions.setBackgroundDrawable(
-                ResourcesCompat.getDrawable(res, 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 BG",
-                textView, lilacDefault, 0);
-
-        // Disable the text view and check that the background has switched to the matching entry
-        // in the default color state list.
-        onView(withId(textViewId)).perform(AppCompatTintableViewActions.setEnabled(false));
-        verifyBackgroundIsColoredAs("Default lilac tinting in disabled state on green BG",
-                textView, lilacDisabled, 0);
-
-        // Enable the text view and check that the background has switched to the matching entry
-        // in the default color state list.
-        onView(withId(textViewId)).perform(AppCompatTintableViewActions.setEnabled(true));
-        verifyBackgroundIsColoredAs("Default lilac tinting in re-enabled state on green BG",
-                textView, lilacDefault, 0);
-
-        // Set a different background on our view based on resource ID
-        onView(withId(textViewId)).perform(AppCompatTintableViewActions.setBackgroundResource(
-                R.drawable.test_background_red));
-
-        // Test the default state for tinting set up in the layout XML file.
-        verifyBackgroundIsColoredAs("Default lilac tinting in enabled state on red BG",
-                textView, lilacDefault, 0);
-
-        // Disable the text view and check that the background has switched to the matching entry
-        // in the default color state list.
-        onView(withId(textViewId)).perform(AppCompatTintableViewActions.setEnabled(false));
-        verifyBackgroundIsColoredAs("Default lilac tinting in disabled state on red BG",
-                textView, lilacDisabled, 0);
-
-        // Enable the text view and check that the background has switched to the matching entry
-        // in the default color state list.
-        onView(withId(textViewId)).perform(AppCompatTintableViewActions.setEnabled(true));
-        verifyBackgroundIsColoredAs("Default lilac tinting in re-enabled state on red BG",
-                textView, lilacDefault, 0);
-    }
-
-    /**
-     * This method tests that translucent background tinting applied to <code>AppCompatTextView</code>
-     * is applied correctly after changing the background itself of the view.
-     */
-    @SmallTest
-    public void testBackgroundTranslucentTintingAcrossBackgroundChange() {
-        final @IdRes int textViewId = R.id.text_view_untinted_no_background;
-        final Resources res = getActivity().getResources();
-
-        @ColorInt int emeraldDefault = ResourcesCompat.getColor(
-                res, R.color.emerald_translucent_default, null);
-        @ColorInt int emeraldDisabled = ResourcesCompat.getColor(
-                res, R.color.emerald_translucent_disabled, null);
-        // This is the fill color of R.drawable.test_background_green set on our text 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 AppCompatTextView textView =
-                (AppCompatTextView) mContainer.findViewById(textViewId);
-
-        assertNull("No background after XML loading", textView.getBackground());
-
-        // Set src_over tint mode on our text view. As the currently set tint list is using
-        // 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(textViewId)).perform(
-                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);
-        onView(withId(textViewId)).perform(
-                AppCompatTintableViewActions.setBackgroundTintList(emeraldColor));
-
-        // Set background on our text view
-        onView(withId(textViewId)).perform(AppCompatTintableViewActions.setBackgroundDrawable(
-                ResourcesCompat.getDrawable(res, 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 text view background. This is due to both translucent colors being used
-        // in the color state list and off-by-one discrepancies of SRC_OVER when it's compositing
-        // translucent color on top of solid fill color. This is where the allowed variance
-        // value of 2 comes from - one for compositing and one for color translucency.
-
-        // Test the default state for tinting set up with the just loaded tint list.
-        verifyBackgroundIsColoredAs("Emerald tinting in enabled state on green BG",
-                textView, ColorUtils.compositeColors(emeraldDefault, backgroundColorGreen), 2);
-
-        // Disable the text view and check that the background has switched to the matching entry
-        // in the default color state list.
-        onView(withId(textViewId)).perform(AppCompatTintableViewActions.setEnabled(false));
-        verifyBackgroundIsColoredAs("Emerald tinting in disabled state on green BG",
-                textView, ColorUtils.compositeColors(emeraldDisabled, backgroundColorGreen), 2);
-
-        // Enable the text view and check that the background has switched to the matching entry
-        // in the default color state list.
-        onView(withId(textViewId)).perform(AppCompatTintableViewActions.setEnabled(true));
-        verifyBackgroundIsColoredAs("Emerald tinting in re-enabled state on green BG",
-                textView, ColorUtils.compositeColors(emeraldDefault, backgroundColorGreen), 2);
-
-        // Set a different background on our view based on resource ID
-        onView(withId(textViewId)).perform(AppCompatTintableViewActions.setBackgroundResource(
-                R.drawable.test_background_red));
-
-        // Test the default state for tinting the new background with the same color state list
-        verifyBackgroundIsColoredAs("Emerald tinting in enabled state on red BG",
-                textView, ColorUtils.compositeColors(emeraldDefault, backgroundColorRed), 2);
-
-        // Disable the text view and check that the background has switched to the matching entry
-        // in our current color state list.
-        onView(withId(textViewId)).perform(AppCompatTintableViewActions.setEnabled(false));
-        verifyBackgroundIsColoredAs("Emerald tinting in disabled state on red BG",
-                textView, ColorUtils.compositeColors(emeraldDisabled, backgroundColorRed), 2);
-
-        // Enable the text view and check that the background has switched to the matching entry
-        // in our current color state list.
-        onView(withId(textViewId)).perform(AppCompatTintableViewActions.setEnabled(true));
-        verifyBackgroundIsColoredAs("Emerald tinting in re-enabled state on red BG",
-                textView, ColorUtils.compositeColors(emeraldDefault, backgroundColorRed), 2);
-    }
 }