/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.support.v7.widget; import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.matcher.ViewMatchers.withId; import static android.support.v7.testutils.TestUtilsActions.setEnabled; 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.test.filters.SmallTest; 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.widget.ImageView; import org.junit.Test; /** * In addition to all tinting-related tests done by the base class, this class provides * testing for tinting image resources on appcompat-v7 view classes that extend directly * or indirectly the core {@link ImageView} class. */ public abstract class AppCompatBaseImageViewTest extends AppCompatBaseViewTest { public AppCompatBaseImageViewTest(Class clazz) { super(clazz); } private void verifyImageSourceIsColoredAs(String description, @NonNull ImageView imageView, @ColorInt int color, int allowedComponentVariance) { Drawable imageSource = imageView.getDrawable(); TestUtils.assertAllPixelsOfColor(description, imageSource, imageSource.getIntrinsicWidth(), imageSource.getIntrinsicHeight(), true, color, allowedComponentVariance, false); } /** * This method tests that image tinting is applied to tintable image view * in enabled and disabled state across a number of ColorStateLists set as * image source tint lists on the same image source. */ @Test @SmallTest public void testImageTintingAcrossStateChange() { final @IdRes int viewId = R.id.view_tinted_source; final Resources res = mActivity.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. verifyImageSourceIsColoredAs("Default lilac tinting in enabled state", view, lilacDefault, 0); // Disable the view and check that the image has switched to the matching entry // in the default color state list. onView(withId(viewId)).perform(setEnabled(false)); verifyImageSourceIsColoredAs("Default lilac tinting in disabled state", view, lilacDisabled, 0); // Enable the view and check that the image has switched to the matching entry // in the default color state list. onView(withId(viewId)).perform(setEnabled(true)); verifyImageSourceIsColoredAs("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 image 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.setImageSourceTintList(sandColor)); verifyImageSourceIsColoredAs("New sand tinting in enabled state", view, sandDefault, 0); // Disable the view and check that the image has switched to the matching entry // in the newly set color state list. onView(withId(viewId)).perform(setEnabled(false)); verifyImageSourceIsColoredAs("New sand tinting in disabled state", view, sandDisabled, 0); // Enable the view and check that the image has switched to the matching entry // in the newly set color state list. onView(withId(viewId)).perform(setEnabled(true)); verifyImageSourceIsColoredAs("New sand tinting in re-enabled state", view, sandDefault, 0); // Load another color state list, set it on the view and check that the image 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.setImageSourceTintList(oceanColor)); verifyImageSourceIsColoredAs("New ocean tinting in enabled state", view, oceanDefault, 0); // Disable the view and check that the image has switched to the matching entry // in the newly set color state list. onView(withId(viewId)).perform(setEnabled(false)); verifyImageSourceIsColoredAs("New ocean tinting in disabled state", view, oceanDisabled, 0); // Enable the view and check that the image has switched to the matching entry // in the newly set color state list. onView(withId(viewId)).perform(setEnabled(true)); verifyImageSourceIsColoredAs("New ocean tinting in re-enabled state", view, oceanDefault, 0); } /** * This method tests that image tinting is applied to tintable image view * in enabled and disabled state across the same image source respects the currently set * image source tinting mode. */ @Test @SmallTest public void testImageTintingAcrossModeChange() { final @IdRes int viewId = R.id.view_untinted_source; final Resources res = mActivity.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_drawable_blue set on our view // that we'll be testing in this method @ColorInt int sourceColor = ResourcesCompat.getColor( res, R.color.test_blue, null); // Test the default state for tinting set up in the layout XML file. verifyImageSourceIsColoredAs("Default no tinting in enabled state", view, sourceColor, 0); // From this point on in this method we're allowing a margin of error in checking the // color of the image source. 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.setImageSourceTintMode(PorterDuff.Mode.SRC_IN)); // Load a new color state list, set it on the view and check that the image 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.setImageSourceTintList(emeraldColor)); verifyImageSourceIsColoredAs("New emerald tinting in enabled state under src_in", view, emeraldDefault, allowedComponentVariance); // Disable the view and check that the image has switched to the matching entry // in the newly set color state list. onView(withId(viewId)).perform(setEnabled(false)); verifyImageSourceIsColoredAs("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 image source 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.setImageSourceTintMode(PorterDuff.Mode.SRC_OVER)); // Enable the view and check that the image has switched to the matching entry // in the color state list. onView(withId(viewId)).perform(setEnabled(true)); verifyImageSourceIsColoredAs("New emerald tinting in enabled state under src_over", view, ColorUtils.compositeColors(emeraldDefault, sourceColor), allowedComponentVariance); // Disable the view and check that the image has switched to the matching entry // in the newly set color state list. onView(withId(viewId)).perform(setEnabled(false)); verifyImageSourceIsColoredAs("New emerald tinting in disabled state under src_over", view, ColorUtils.compositeColors(emeraldDisabled, sourceColor), allowedComponentVariance); } /** * This method tests that opaque tinting applied to tintable image source * is applied correctly after changing the image source itself. */ @Test @SmallTest public void testImageOpaqueTintingAcrossImageChange() { final @IdRes int viewId = R.id.view_tinted_no_source; final Resources res = mActivity.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); // Set image source on our view onView(withId(viewId)).perform(AppCompatTintableViewActions.setImageResource( R.drawable.test_drawable_green)); // Test the default state for tinting set up in the layout XML file. verifyImageSourceIsColoredAs("Default lilac tinting in enabled state on green source", view, lilacDefault, 0); // Disable the view and check that the image has switched to the matching entry // in the default color state list. onView(withId(viewId)).perform(setEnabled(false)); verifyImageSourceIsColoredAs("Default lilac tinting in disabled state on green source", view, lilacDisabled, 0); // Enable the view and check that the image has switched to the matching entry // in the default color state list. onView(withId(viewId)).perform(setEnabled(true)); verifyImageSourceIsColoredAs("Default lilac tinting in re-enabled state on green source", view, lilacDefault, 0); // Set a different image source on our view based on resource ID onView(withId(viewId)).perform(AppCompatTintableViewActions.setImageResource( R.drawable.test_drawable_red)); // Test the default state for tinting set up in the layout XML file. verifyImageSourceIsColoredAs("Default lilac tinting in enabled state on red source", view, lilacDefault, 0); // Disable the view and check that the image has switched to the matching entry // in the default color state list. onView(withId(viewId)).perform(setEnabled(false)); verifyImageSourceIsColoredAs("Default lilac tinting in disabled state on red source", view, lilacDisabled, 0); // Enable the view and check that the image has switched to the matching entry // in the default color state list. onView(withId(viewId)).perform(setEnabled(true)); verifyImageSourceIsColoredAs("Default lilac tinting in re-enabled state on red source", view, lilacDefault, 0); } /** * This method tests that translucent tinting applied to tintable image source * is applied correctly after changing the image source itself. */ @Test @SmallTest public void testImageTranslucentTintingAcrossImageChange() { final @IdRes int viewId = R.id.view_untinted_no_source; final Resources res = mActivity.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_drawable_green that will be set on our view // that we'll be testing in this method @ColorInt int colorGreen = ResourcesCompat.getColor( res, R.color.test_green, null); // This is the fill color of R.drawable.test_drawable_red that will be set on our view // that we'll be testing in this method @ColorInt int colorRed = ResourcesCompat.getColor( res, R.color.test_red, null); // Set src_over tint mode on our view. As the currently set tint list is using // translucent colors, we expect the actual image source 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.setImageSourceTintMode(PorterDuff.Mode.SRC_OVER)); // Load and set a translucent color state list as the image source tint list final ColorStateList emeraldColor = ResourcesCompat.getColorStateList( res, R.color.color_state_list_emerald_translucent, null); onView(withId(viewId)).perform( AppCompatTintableViewActions.setImageSourceTintList(emeraldColor)); // Set image source on our view onView(withId(viewId)).perform(AppCompatTintableViewActions.setImageResource( R.drawable.test_drawable_green)); // From this point on in this method we're allowing a margin of error in checking the // color of the image source. 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. verifyImageSourceIsColoredAs("Emerald tinting in enabled state on green source", view, ColorUtils.compositeColors(emeraldDefault, colorGreen), allowedComponentVariance); // Disable the view and check that the image has switched to the matching entry // in the default color state list. onView(withId(viewId)).perform(setEnabled(false)); verifyImageSourceIsColoredAs("Emerald tinting in disabled state on green source", view, ColorUtils.compositeColors(emeraldDisabled, colorGreen), allowedComponentVariance); // Enable the view and check that the image has switched to the matching entry // in the default color state list. onView(withId(viewId)).perform(setEnabled(true)); verifyImageSourceIsColoredAs("Emerald tinting in re-enabled state on green source", view, ColorUtils.compositeColors(emeraldDefault, colorGreen), allowedComponentVariance); // Set a different image source on our view based on resource ID onView(withId(viewId)).perform(AppCompatTintableViewActions.setImageResource( R.drawable.test_drawable_red)); // Test the default state for tinting the new image with the same color state list verifyImageSourceIsColoredAs("Emerald tinting in enabled state on red source", view, ColorUtils.compositeColors(emeraldDefault, colorRed), allowedComponentVariance); // Disable the view and check that the image has switched to the matching entry // in our current color state list. onView(withId(viewId)).perform(setEnabled(false)); verifyImageSourceIsColoredAs("Emerald tinting in disabled state on red source", view, ColorUtils.compositeColors(emeraldDisabled, colorRed), allowedComponentVariance); // Enable the view and check that the image has switched to the matching entry // in our current color state list. onView(withId(viewId)).perform(setEnabled(true)); verifyImageSourceIsColoredAs("Emerald tinting in re-enabled state on red source", view, ColorUtils.compositeColors(emeraldDefault, colorRed), allowedComponentVariance); } /** * This method tests that background tinting applied on a tintable image view does not * affect the tinting of the image source. */ @Test @SmallTest public void testImageTintingAcrossBackgroundTintingChange() { final @IdRes int viewId = R.id.view_untinted_source; final Resources res = mActivity.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); // This is the fill color of R.drawable.test_drawable_blue set on our view // that we'll be testing in this method @ColorInt int sourceColor = ResourcesCompat.getColor( res, R.color.test_blue, null); @ColorInt int newSourceColor = ResourcesCompat.getColor( res, R.color.test_red, null); // Test the default state for tinting set up in the layout XML file. verifyImageSourceIsColoredAs("Default no tinting in enabled state", view, sourceColor, 0); // Change background tinting of our image final ColorStateList lilacColor = ResourcesCompat.getColorStateList( mResources, R.color.color_state_list_lilac, null); onView(withId(viewId)).perform( AppCompatTintableViewActions.setBackgroundResource( R.drawable.test_background_green)); onView(withId(viewId)).perform( AppCompatTintableViewActions.setBackgroundTintMode(PorterDuff.Mode.SRC_IN)); onView(withId(viewId)).perform( AppCompatTintableViewActions.setBackgroundTintList(lilacColor)); // Verify that the image still has the original color (untinted) verifyImageSourceIsColoredAs("No image tinting after change in background tinting", view, sourceColor, 0); // Now set a different image source onView(withId(viewId)).perform( AppCompatTintableViewActions.setImageResource(R.drawable.test_drawable_red)); // And verify that the image has the new color (untinted) verifyImageSourceIsColoredAs("No image tinting after change of image source", view, newSourceColor, 0); // Change the background tinting again final ColorStateList sandColor = ResourcesCompat.getColorStateList( mResources, R.color.color_state_list_sand, null); onView(withId(viewId)).perform( AppCompatTintableViewActions.setBackgroundTintList(sandColor)); // And verify that the image still has the same new color (untinted) verifyImageSourceIsColoredAs("No image tinting after change in background tinting", view, newSourceColor, 0); // Now set up image tinting on our view. We're using a color state list with fully // opaque colors, and we expect the matching entry in that list to be applied on the // image source (ignoring the background tinting) onView(withId(viewId)).perform( AppCompatTintableViewActions.setImageSourceTintMode(PorterDuff.Mode.SRC_IN)); onView(withId(viewId)).perform( AppCompatTintableViewActions.setImageSourceTintList(lilacColor)); verifyImageSourceIsColoredAs("New lilac image tinting", view, lilacDefault, 0); } }