Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package androidx.appcompat.widget;
     18 
     19 import static android.support.test.espresso.Espresso.onView;
     20 import static android.support.test.espresso.matcher.ViewMatchers.withId;
     21 
     22 import static androidx.appcompat.testutils.TestUtilsActions.setEnabled;
     23 
     24 import android.content.res.ColorStateList;
     25 import android.content.res.Resources;
     26 import android.graphics.PorterDuff;
     27 import android.graphics.drawable.Drawable;
     28 import android.support.test.filters.SmallTest;
     29 import android.widget.ImageView;
     30 
     31 import androidx.annotation.ColorInt;
     32 import androidx.annotation.IdRes;
     33 import androidx.annotation.NonNull;
     34 import androidx.appcompat.test.R;
     35 import androidx.appcompat.testutils.AppCompatTintableViewActions;
     36 import androidx.appcompat.testutils.BaseTestActivity;
     37 import androidx.appcompat.testutils.TestUtils;
     38 import androidx.core.content.res.ResourcesCompat;
     39 import androidx.core.graphics.ColorUtils;
     40 
     41 import org.junit.Test;
     42 
     43 /**
     44  * In addition to all tinting-related tests done by the base class, this class provides
     45  * testing for tinting image resources on appcompat-v7 view classes that extend directly
     46  * or indirectly the core {@link ImageView} class.
     47  */
     48 public abstract class AppCompatBaseImageViewTest<T extends ImageView>
     49         extends AppCompatBaseViewTest<BaseTestActivity, T> {
     50     public AppCompatBaseImageViewTest(Class clazz) {
     51         super(clazz);
     52     }
     53 
     54     private void verifyImageSourceIsColoredAs(String description, @NonNull ImageView imageView,
     55             @ColorInt int color, int allowedComponentVariance) {
     56         Drawable imageSource = imageView.getDrawable();
     57         TestUtils.assertAllPixelsOfColor(description,
     58                 imageSource, imageSource.getIntrinsicWidth(), imageSource.getIntrinsicHeight(),
     59                 true, color, allowedComponentVariance, false);
     60     }
     61 
     62     /**
     63      * This method tests that image tinting is applied to tintable image view
     64      * in enabled and disabled state across a number of <code>ColorStateList</code>s set as
     65      * image source tint lists on the same image source.
     66      */
     67     @Test
     68     @SmallTest
     69     public void testImageTintingAcrossStateChange() {
     70         final @IdRes int viewId = R.id.view_tinted_source;
     71         final Resources res = mActivity.getResources();
     72         final T view = (T) mContainer.findViewById(viewId);
     73 
     74         @ColorInt int lilacDefault = ResourcesCompat.getColor(res, R.color.lilac_default, null);
     75         @ColorInt int lilacDisabled = ResourcesCompat.getColor(res, R.color.lilac_disabled, null);
     76         @ColorInt int sandDefault = ResourcesCompat.getColor(res, R.color.sand_default, null);
     77         @ColorInt int sandDisabled = ResourcesCompat.getColor(res, R.color.sand_disabled, null);
     78         @ColorInt int oceanDefault = ResourcesCompat.getColor(res, R.color.ocean_default, null);
     79         @ColorInt int oceanDisabled = ResourcesCompat.getColor(res, R.color.ocean_disabled, null);
     80 
     81         // Test the default state for tinting set up in the layout XML file.
     82         verifyImageSourceIsColoredAs("Default lilac tinting in enabled state", view,
     83                 lilacDefault, 0);
     84 
     85         // Disable the view and check that the image has switched to the matching entry
     86         // in the default color state list.
     87         onView(withId(viewId)).perform(setEnabled(false));
     88         verifyImageSourceIsColoredAs("Default lilac tinting in disabled state", view,
     89                 lilacDisabled, 0);
     90 
     91         // Enable the view and check that the image has switched to the matching entry
     92         // in the default color state list.
     93         onView(withId(viewId)).perform(setEnabled(true));
     94         verifyImageSourceIsColoredAs("Default lilac tinting in re-enabled state", view,
     95                 lilacDefault, 0);
     96 
     97         // Load a new color state list, set it on the view and check that the image has
     98         // switched to the matching entry in newly set color state list.
     99         final ColorStateList sandColor = ResourcesCompat.getColorStateList(
    100                 res, R.color.color_state_list_sand, null);
    101         onView(withId(viewId)).perform(
    102                 AppCompatTintableViewActions.setImageSourceTintList(sandColor));
    103         verifyImageSourceIsColoredAs("New sand tinting in enabled state", view,
    104                 sandDefault, 0);
    105 
    106         // Disable the view and check that the image has switched to the matching entry
    107         // in the newly set color state list.
    108         onView(withId(viewId)).perform(setEnabled(false));
    109         verifyImageSourceIsColoredAs("New sand tinting in disabled state", view,
    110                 sandDisabled, 0);
    111 
    112         // Enable the view and check that the image has switched to the matching entry
    113         // in the newly set color state list.
    114         onView(withId(viewId)).perform(setEnabled(true));
    115         verifyImageSourceIsColoredAs("New sand tinting in re-enabled state", view,
    116                 sandDefault, 0);
    117 
    118         // Load another color state list, set it on the view and check that the image has
    119         // switched to the matching entry in newly set color state list.
    120         final ColorStateList oceanColor = ResourcesCompat.getColorStateList(
    121                 res, R.color.color_state_list_ocean, null);
    122         onView(withId(viewId)).perform(
    123                 AppCompatTintableViewActions.setImageSourceTintList(oceanColor));
    124         verifyImageSourceIsColoredAs("New ocean tinting in enabled state", view,
    125                 oceanDefault, 0);
    126 
    127         // Disable the view and check that the image has switched to the matching entry
    128         // in the newly set color state list.
    129         onView(withId(viewId)).perform(setEnabled(false));
    130         verifyImageSourceIsColoredAs("New ocean tinting in disabled state", view,
    131                 oceanDisabled, 0);
    132 
    133         // Enable the view and check that the image has switched to the matching entry
    134         // in the newly set color state list.
    135         onView(withId(viewId)).perform(setEnabled(true));
    136         verifyImageSourceIsColoredAs("New ocean tinting in re-enabled state", view,
    137                 oceanDefault, 0);
    138     }
    139 
    140     /**
    141      * This method tests that image tinting is applied to tintable image view
    142      * in enabled and disabled state across the same image source respects the currently set
    143      * image source tinting mode.
    144      */
    145     @Test
    146     @SmallTest
    147     public void testImageTintingAcrossModeChange() {
    148         final @IdRes int viewId = R.id.view_untinted_source;
    149         final Resources res = mActivity.getResources();
    150         final T view = (T) mContainer.findViewById(viewId);
    151 
    152         @ColorInt int emeraldDefault = ResourcesCompat.getColor(
    153                 res, R.color.emerald_translucent_default, null);
    154         @ColorInt int emeraldDisabled = ResourcesCompat.getColor(
    155                 res, R.color.emerald_translucent_disabled, null);
    156         // This is the fill color of R.drawable.test_drawable_blue set on our view
    157         // that we'll be testing in this method
    158         @ColorInt int sourceColor = ResourcesCompat.getColor(
    159                 res, R.color.test_blue, null);
    160 
    161         // Test the default state for tinting set up in the layout XML file.
    162         verifyImageSourceIsColoredAs("Default no tinting in enabled state", view,
    163                 sourceColor, 0);
    164 
    165         // From this point on in this method we're allowing a margin of error in checking the
    166         // color of the image source. This is due to both translucent colors being used
    167         // in the color state list and off-by-one discrepancies of SRC_OVER when it's compositing
    168         // translucent color on top of solid fill color. This is where the allowed variance
    169         // value of 2 comes from - one for compositing and one for color translucency.
    170         final int allowedComponentVariance = 2;
    171 
    172         // Set src_in tint mode on our view
    173         onView(withId(viewId)).perform(
    174                 AppCompatTintableViewActions.setImageSourceTintMode(PorterDuff.Mode.SRC_IN));
    175 
    176         // Load a new color state list, set it on the view and check that the image has
    177         // switched to the matching entry in newly set color state list.
    178         final ColorStateList emeraldColor = ResourcesCompat.getColorStateList(
    179                 res, R.color.color_state_list_emerald_translucent, null);
    180         onView(withId(viewId)).perform(
    181                 AppCompatTintableViewActions.setImageSourceTintList(emeraldColor));
    182         verifyImageSourceIsColoredAs("New emerald tinting in enabled state under src_in", view,
    183                 emeraldDefault, allowedComponentVariance);
    184 
    185         // Disable the view and check that the image has switched to the matching entry
    186         // in the newly set color state list.
    187         onView(withId(viewId)).perform(setEnabled(false));
    188         verifyImageSourceIsColoredAs("New emerald tinting in disabled state under src_in", view,
    189                 emeraldDisabled, allowedComponentVariance);
    190 
    191         // Set src_over tint mode on our view. As the currently set tint list is using
    192         // translucent colors, we expect the actual image source of the view to be different under
    193         // this new mode (unlike src_in and src_over that behave identically when the destination is
    194         // a fully filled rectangle and the source is an opaque color).
    195         onView(withId(viewId)).perform(
    196                 AppCompatTintableViewActions.setImageSourceTintMode(PorterDuff.Mode.SRC_OVER));
    197 
    198         // Enable the view and check that the image has switched to the matching entry
    199         // in the color state list.
    200         onView(withId(viewId)).perform(setEnabled(true));
    201         verifyImageSourceIsColoredAs("New emerald tinting in enabled state under src_over", view,
    202                 ColorUtils.compositeColors(emeraldDefault, sourceColor),
    203                 allowedComponentVariance);
    204 
    205         // Disable the view and check that the image has switched to the matching entry
    206         // in the newly set color state list.
    207         onView(withId(viewId)).perform(setEnabled(false));
    208         verifyImageSourceIsColoredAs("New emerald tinting in disabled state under src_over",
    209                 view, ColorUtils.compositeColors(emeraldDisabled, sourceColor),
    210                 allowedComponentVariance);
    211     }
    212 
    213     /**
    214      * This method tests that opaque tinting applied to tintable image source
    215      * is applied correctly after changing the image source itself.
    216      */
    217     @Test
    218     @SmallTest
    219     public void testImageOpaqueTintingAcrossImageChange() {
    220         final @IdRes int viewId = R.id.view_tinted_no_source;
    221         final Resources res = mActivity.getResources();
    222         final T view = (T) mContainer.findViewById(viewId);
    223 
    224         @ColorInt int lilacDefault = ResourcesCompat.getColor(res, R.color.lilac_default, null);
    225         @ColorInt int lilacDisabled = ResourcesCompat.getColor(res, R.color.lilac_disabled, null);
    226 
    227         // Set image source on our view
    228         onView(withId(viewId)).perform(AppCompatTintableViewActions.setImageResource(
    229                 R.drawable.test_drawable_green));
    230 
    231         // Test the default state for tinting set up in the layout XML file.
    232         verifyImageSourceIsColoredAs("Default lilac tinting in enabled state on green source",
    233                 view, lilacDefault, 0);
    234 
    235         // Disable the view and check that the image has switched to the matching entry
    236         // in the default color state list.
    237         onView(withId(viewId)).perform(setEnabled(false));
    238         verifyImageSourceIsColoredAs("Default lilac tinting in disabled state on green source",
    239                 view, lilacDisabled, 0);
    240 
    241         // Enable the view and check that the image has switched to the matching entry
    242         // in the default color state list.
    243         onView(withId(viewId)).perform(setEnabled(true));
    244         verifyImageSourceIsColoredAs("Default lilac tinting in re-enabled state on green source",
    245                 view, lilacDefault, 0);
    246 
    247         // Set a different image source on our view based on resource ID
    248         onView(withId(viewId)).perform(AppCompatTintableViewActions.setImageResource(
    249                 R.drawable.test_drawable_red));
    250 
    251         // Test the default state for tinting set up in the layout XML file.
    252         verifyImageSourceIsColoredAs("Default lilac tinting in enabled state on red source",
    253                 view, lilacDefault, 0);
    254 
    255         // Disable the view and check that the image has switched to the matching entry
    256         // in the default color state list.
    257         onView(withId(viewId)).perform(setEnabled(false));
    258         verifyImageSourceIsColoredAs("Default lilac tinting in disabled state on red source",
    259                 view, lilacDisabled, 0);
    260 
    261         // Enable the view and check that the image has switched to the matching entry
    262         // in the default color state list.
    263         onView(withId(viewId)).perform(setEnabled(true));
    264         verifyImageSourceIsColoredAs("Default lilac tinting in re-enabled state on red source",
    265                 view, lilacDefault, 0);
    266     }
    267 
    268     /**
    269      * This method tests that translucent tinting applied to tintable image source
    270      * is applied correctly after changing the image source itself.
    271      */
    272     @Test
    273     @SmallTest
    274     public void testImageTranslucentTintingAcrossImageChange() {
    275         final @IdRes int viewId = R.id.view_untinted_no_source;
    276         final Resources res = mActivity.getResources();
    277         final T view = (T) mContainer.findViewById(viewId);
    278 
    279         @ColorInt int emeraldDefault = ResourcesCompat.getColor(
    280                 res, R.color.emerald_translucent_default, null);
    281         @ColorInt int emeraldDisabled = ResourcesCompat.getColor(
    282                 res, R.color.emerald_translucent_disabled, null);
    283         // This is the fill color of R.drawable.test_drawable_green that will be set on our view
    284         // that we'll be testing in this method
    285         @ColorInt int colorGreen = ResourcesCompat.getColor(
    286                 res, R.color.test_green, null);
    287         // This is the fill color of R.drawable.test_drawable_red that will be set on our view
    288         // that we'll be testing in this method
    289         @ColorInt int colorRed = ResourcesCompat.getColor(
    290                 res, R.color.test_red, null);
    291 
    292         // Set src_over tint mode on our view. As the currently set tint list is using
    293         // translucent colors, we expect the actual image source of the view to be different under
    294         // this new mode (unlike src_in and src_over that behave identically when the destination is
    295         // a fully filled rectangle and the source is an opaque color).
    296         onView(withId(viewId)).perform(
    297                 AppCompatTintableViewActions.setImageSourceTintMode(PorterDuff.Mode.SRC_OVER));
    298         // Load and set a translucent color state list as the image source tint list
    299         final ColorStateList emeraldColor = ResourcesCompat.getColorStateList(
    300                 res, R.color.color_state_list_emerald_translucent, null);
    301         onView(withId(viewId)).perform(
    302                 AppCompatTintableViewActions.setImageSourceTintList(emeraldColor));
    303 
    304         // Set image source on our view
    305         onView(withId(viewId)).perform(AppCompatTintableViewActions.setImageResource(
    306                 R.drawable.test_drawable_green));
    307 
    308         // From this point on in this method we're allowing a margin of error in checking the
    309         // color of the image source. This is due to both translucent colors being used
    310         // in the color state list and off-by-one discrepancies of SRC_OVER when it's compositing
    311         // translucent color on top of solid fill color. This is where the allowed variance
    312         // value of 2 comes from - one for compositing and one for color translucency.
    313         final int allowedComponentVariance = 2;
    314 
    315         // Test the default state for tinting set up with the just loaded tint list.
    316         verifyImageSourceIsColoredAs("Emerald tinting in enabled state on green source",
    317                 view, ColorUtils.compositeColors(emeraldDefault, colorGreen),
    318                 allowedComponentVariance);
    319 
    320         // Disable the view and check that the image has switched to the matching entry
    321         // in the default color state list.
    322         onView(withId(viewId)).perform(setEnabled(false));
    323         verifyImageSourceIsColoredAs("Emerald tinting in disabled state on green source",
    324                 view, ColorUtils.compositeColors(emeraldDisabled, colorGreen),
    325                 allowedComponentVariance);
    326 
    327         // Enable the view and check that the image has switched to the matching entry
    328         // in the default color state list.
    329         onView(withId(viewId)).perform(setEnabled(true));
    330         verifyImageSourceIsColoredAs("Emerald tinting in re-enabled state on green source",
    331                 view, ColorUtils.compositeColors(emeraldDefault, colorGreen),
    332                 allowedComponentVariance);
    333 
    334         // Set a different image source on our view based on resource ID
    335         onView(withId(viewId)).perform(AppCompatTintableViewActions.setImageResource(
    336                 R.drawable.test_drawable_red));
    337 
    338         // Test the default state for tinting the new image with the same color state list
    339         verifyImageSourceIsColoredAs("Emerald tinting in enabled state on red source",
    340                 view, ColorUtils.compositeColors(emeraldDefault, colorRed),
    341                 allowedComponentVariance);
    342 
    343         // Disable the view and check that the image has switched to the matching entry
    344         // in our current color state list.
    345         onView(withId(viewId)).perform(setEnabled(false));
    346         verifyImageSourceIsColoredAs("Emerald tinting in disabled state on red source",
    347                 view, ColorUtils.compositeColors(emeraldDisabled, colorRed),
    348                 allowedComponentVariance);
    349 
    350         // Enable the view and check that the image has switched to the matching entry
    351         // in our current color state list.
    352         onView(withId(viewId)).perform(setEnabled(true));
    353         verifyImageSourceIsColoredAs("Emerald tinting in re-enabled state on red source",
    354                 view, ColorUtils.compositeColors(emeraldDefault, colorRed),
    355                 allowedComponentVariance);
    356     }
    357 
    358     /**
    359      * This method tests that background tinting applied on a tintable image view does not
    360      * affect the tinting of the image source.
    361      */
    362     @Test
    363     @SmallTest
    364     public void testImageTintingAcrossBackgroundTintingChange() {
    365         final @IdRes int viewId = R.id.view_untinted_source;
    366         final Resources res = mActivity.getResources();
    367         final T view = (T) mContainer.findViewById(viewId);
    368 
    369         @ColorInt int lilacDefault = ResourcesCompat.getColor(res, R.color.lilac_default, null);
    370         @ColorInt int lilacDisabled = ResourcesCompat.getColor(res, R.color.lilac_disabled, null);
    371         // This is the fill color of R.drawable.test_drawable_blue set on our view
    372         // that we'll be testing in this method
    373         @ColorInt int sourceColor = ResourcesCompat.getColor(
    374                 res, R.color.test_blue, null);
    375         @ColorInt int newSourceColor = ResourcesCompat.getColor(
    376                 res, R.color.test_red, null);
    377 
    378         // Test the default state for tinting set up in the layout XML file.
    379         verifyImageSourceIsColoredAs("Default no tinting in enabled state", view,
    380                 sourceColor, 0);
    381 
    382         // Change background tinting of our image
    383         final ColorStateList lilacColor = ResourcesCompat.getColorStateList(
    384                 mResources, R.color.color_state_list_lilac, null);
    385         onView(withId(viewId)).perform(
    386                 AppCompatTintableViewActions.setBackgroundResource(
    387                         R.drawable.test_background_green));
    388         onView(withId(viewId)).perform(
    389                 AppCompatTintableViewActions.setBackgroundTintMode(PorterDuff.Mode.SRC_IN));
    390         onView(withId(viewId)).perform(
    391                 AppCompatTintableViewActions.setBackgroundTintList(lilacColor));
    392 
    393         // Verify that the image still has the original color (untinted)
    394         verifyImageSourceIsColoredAs("No image tinting after change in background tinting", view,
    395                 sourceColor, 0);
    396 
    397         // Now set a different image source
    398         onView(withId(viewId)).perform(
    399                 AppCompatTintableViewActions.setImageResource(R.drawable.test_drawable_red));
    400         // And verify that the image has the new color (untinted)
    401         verifyImageSourceIsColoredAs("No image tinting after change of image source", view,
    402                 newSourceColor, 0);
    403 
    404         // Change the background tinting again
    405         final ColorStateList sandColor = ResourcesCompat.getColorStateList(
    406                 mResources, R.color.color_state_list_sand, null);
    407         onView(withId(viewId)).perform(
    408                 AppCompatTintableViewActions.setBackgroundTintList(sandColor));
    409         // And verify that the image still has the same new color (untinted)
    410         verifyImageSourceIsColoredAs("No image tinting after change in background tinting", view,
    411                 newSourceColor, 0);
    412 
    413         // Now set up image tinting on our view. We're using a color state list with fully
    414         // opaque colors, and we expect the matching entry in that list to be applied on the
    415         // image source (ignoring the background tinting)
    416         onView(withId(viewId)).perform(
    417                 AppCompatTintableViewActions.setImageSourceTintMode(PorterDuff.Mode.SRC_IN));
    418         onView(withId(viewId)).perform(
    419                 AppCompatTintableViewActions.setImageSourceTintList(lilacColor));
    420         verifyImageSourceIsColoredAs("New lilac image tinting", view,
    421                 lilacDefault, 0);
    422     }
    423 }
    424