Home | History | Annotate | Download | only in app
      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 android.support.v7.app;
     18 
     19 import static junit.framework.Assert.assertEquals;
     20 
     21 import static org.junit.Assert.assertNull;
     22 import static org.junit.Assert.assertSame;
     23 
     24 import android.content.res.ColorStateList;
     25 import android.content.res.Resources;
     26 import android.graphics.Color;
     27 import android.graphics.PorterDuff;
     28 import android.graphics.drawable.Drawable;
     29 import android.support.annotation.ColorInt;
     30 import android.support.annotation.NonNull;
     31 import android.support.test.annotation.UiThreadTest;
     32 import android.support.test.filters.SmallTest;
     33 import android.support.test.rule.ActivityTestRule;
     34 import android.support.test.runner.AndroidJUnit4;
     35 import android.support.v4.content.res.ResourcesCompat;
     36 import android.support.v4.graphics.ColorUtils;
     37 import android.support.v4.view.MenuItemCompat;
     38 import android.support.v7.appcompat.test.R;
     39 import android.support.v7.testutils.TestUtils;
     40 import android.view.Menu;
     41 import android.view.MenuItem;
     42 
     43 import org.junit.Before;
     44 import org.junit.Rule;
     45 import org.junit.Test;
     46 import org.junit.runner.RunWith;
     47 
     48 /**
     49  * Test icon tinting in {@link MenuItem}s
     50  */
     51 @SmallTest
     52 @RunWith(AndroidJUnit4.class)
     53 public class AppCompatMenuItemIconTintingTest {
     54     private AppCompatMenuItemIconTintingTestActivity mActivity;
     55     private Resources mResources;
     56     private Menu mMenu;
     57 
     58     @Rule
     59     public ActivityTestRule<AppCompatMenuItemIconTintingTestActivity> mActivityTestRule =
     60             new ActivityTestRule<>(AppCompatMenuItemIconTintingTestActivity.class);
     61 
     62     @Before
     63     public void setup() {
     64         mActivity = mActivityTestRule.getActivity();
     65         mResources = mActivity.getResources();
     66         mMenu = mActivity.getToolbarMenu();
     67     }
     68 
     69     @UiThreadTest
     70     @Test
     71     public void testIconTinting() throws Throwable {
     72         final MenuItem firstItem = mMenu.getItem(0);
     73         final MenuItem secondItem = mMenu.getItem(1);
     74         final MenuItem thirdItem = mMenu.getItem(2);
     75 
     76         // These are the default set in layout XML
     77         assertNull(MenuItemCompat.getIconTintMode(firstItem));
     78         assertEquals(Color.WHITE, MenuItemCompat.getIconTintList(firstItem).getDefaultColor());
     79 
     80         assertEquals(PorterDuff.Mode.SCREEN, MenuItemCompat.getIconTintMode(secondItem));
     81         assertNull(MenuItemCompat.getIconTintList(secondItem));
     82 
     83         assertNull(MenuItemCompat.getIconTintMode(thirdItem));
     84         assertNull(MenuItemCompat.getIconTintList(thirdItem));
     85 
     86         // Change tint color list and mode and verify that they are returned by the getters
     87         final ColorStateList colors = ColorStateList.valueOf(Color.RED);
     88 
     89         MenuItemCompat.setIconTintList(firstItem, colors);
     90         MenuItemCompat.setIconTintMode(firstItem, PorterDuff.Mode.XOR);
     91         assertSame(colors, MenuItemCompat.getIconTintList(firstItem));
     92         assertEquals(PorterDuff.Mode.XOR, MenuItemCompat.getIconTintMode(firstItem));
     93 
     94         // Ensure the tint is preserved across drawable changes.
     95         firstItem.setIcon(R.drawable.icon_yellow);
     96         assertSame(colors, MenuItemCompat.getIconTintList(firstItem));
     97         assertEquals(PorterDuff.Mode.XOR, MenuItemCompat.getIconTintMode(firstItem));
     98 
     99         // Change tint color list and mode again and verify that they are returned by the getters
    100         final ColorStateList colorsNew = ColorStateList.valueOf(Color.MAGENTA);
    101         MenuItemCompat.setIconTintList(firstItem, colorsNew);
    102         MenuItemCompat.setIconTintMode(firstItem, PorterDuff.Mode.SRC_IN);
    103         assertSame(colorsNew, MenuItemCompat.getIconTintList(firstItem));
    104         assertEquals(PorterDuff.Mode.SRC_IN, MenuItemCompat.getIconTintMode(firstItem));
    105     }
    106 
    107     private void verifyIconIsColoredAs(String description, @NonNull Drawable icon,
    108             @ColorInt int color, int allowedComponentVariance) {
    109         TestUtils.assertAllPixelsOfColor(description,
    110                 icon, icon.getIntrinsicWidth(), icon.getIntrinsicHeight(), true,
    111                 color, allowedComponentVariance, false);
    112     }
    113 
    114 
    115     /**
    116      * This method tests that icon tinting is not applied when the
    117      * menu item has no icon.
    118      */
    119     @UiThreadTest
    120     @Test
    121     public void testIconTintingWithNoIcon() {
    122         final MenuItem sixthItem = mMenu.getItem(5);
    123 
    124         // Note that all the asserts in this test check that the menu item icon
    125         // is null. This is because the matching entry in the XML doesn't define any
    126         // icon, and there is nothing to tint.
    127         assertNull("No icon after XML loading", sixthItem.getIcon());
    128 
    129         // Load a new color state list, set it on the menu item icon and check that the icon
    130         // is still null.
    131         final ColorStateList sandColor = ResourcesCompat.getColorStateList(
    132                 mResources, R.color.color_state_list_sand, null);
    133         MenuItemCompat.setIconTintList(sixthItem, sandColor);
    134         assertNull("No icon after setting icon tint list", sixthItem.getIcon());
    135 
    136         // Set tint mode on the menu item icon and check that the icon is still null.
    137         MenuItemCompat.setIconTintMode(sixthItem, PorterDuff.Mode.MULTIPLY);
    138         assertNull("No icon after setting icon tint mode", sixthItem.getIcon());
    139     }
    140 
    141     /**
    142      * This method tests that icon tinting is applied across a number of
    143      * <code>ColorStateList</code>s set as icon tint lists on the same menu item.
    144      */
    145     @UiThreadTest
    146     @Test
    147     public void testIconTintingAcrossTintListChange() {
    148         final MenuItem firstItem = mMenu.getItem(0);
    149 
    150         final @ColorInt int sandDefault = ResourcesCompat.getColor(
    151                 mResources, R.color.sand_default, null);
    152         final @ColorInt int oceanDefault = ResourcesCompat.getColor(
    153                 mResources, R.color.ocean_default, null);
    154 
    155         // Test the default state for tinting set up in the menu XML file.
    156         verifyIconIsColoredAs("Default white tinting", firstItem.getIcon(), Color.WHITE, 0);
    157 
    158         // Load a new color state list, set it on the menu item and check that the icon has
    159         // switched to the matching entry in newly set color state list.
    160         final ColorStateList sandColor = ResourcesCompat.getColorStateList(
    161                 mResources, R.color.color_state_list_sand, null);
    162         MenuItemCompat.setIconTintList(firstItem, sandColor);
    163         verifyIconIsColoredAs("Default white tinting", firstItem.getIcon(), sandDefault, 0);
    164 
    165         // Load another color state list, set it on the menu item and check that the icon has
    166         // switched to the matching entry in newly set color state list.
    167         final ColorStateList oceanColor = ResourcesCompat.getColorStateList(
    168                 mResources, R.color.color_state_list_ocean, null);
    169         MenuItemCompat.setIconTintList(firstItem, oceanColor);
    170         verifyIconIsColoredAs("Default white tinting", firstItem.getIcon(), oceanDefault, 0);
    171     }
    172 
    173     /**
    174      * This method tests that opaque icon tinting is applied correctly after changing the icon
    175      * itself of the menu item.
    176      */
    177     @UiThreadTest
    178     @Test
    179     public void testIconOpaqueTintingAcrossIconChange() {
    180         final MenuItem secondItem = mMenu.getItem(1);
    181 
    182         // This is the fill color of R.drawable.icon_black set on our menu icon
    183         // that we'll be testing in this method
    184         final @ColorInt int iconColorBlack = 0xFF000000;
    185 
    186         // At this point we shouldn't have any tinting since it's not defined in the menu XML
    187         verifyIconIsColoredAs("Black icon before any tinting", secondItem.getIcon(),
    188                 iconColorBlack, 0);
    189 
    190         // Now set up the tinting
    191         final ColorStateList lilacColor = ResourcesCompat.getColorStateList(
    192                 mResources, R.color.color_state_list_lilac, null);
    193         final @ColorInt int lilacDefault = ResourcesCompat.getColor(
    194                 mResources, R.color.lilac_default, null);
    195         MenuItemCompat.setIconTintList(secondItem, lilacColor);
    196         MenuItemCompat.setIconTintMode(secondItem, PorterDuff.Mode.SRC_OVER);
    197 
    198         // Check that the icon is now tinted
    199         verifyIconIsColoredAs("Lilac icon after tinting the black icon",
    200                 secondItem.getIcon(), lilacDefault, 0);
    201 
    202         // Set a different icon on our menu item
    203         secondItem.setIcon(R.drawable.test_drawable_red);
    204 
    205         // Check that the icon is still tinted with the same color as before
    206         verifyIconIsColoredAs("Lilac icon after changing icon to red",
    207                 secondItem.getIcon(), lilacDefault, 0);
    208     }
    209 
    210     /**
    211      * This method tests that translucent icon tinting is applied correctly after changing the icon
    212      * itself of the menu item.
    213      */
    214     @UiThreadTest
    215     @Test
    216     public void testIconTranslucentTintingAcrossIconChange() {
    217         final MenuItem secondItem = mMenu.getItem(1);
    218 
    219         // This is the fill color of R.drawable.icon_black set on our menu icon
    220         // that we'll be testing in this method
    221         final @ColorInt int iconColorBlack = 0xFF000000;
    222 
    223         // At this point we shouldn't have any tinting since it's not defined in the menu XML
    224         verifyIconIsColoredAs("Black icon before any tinting", secondItem.getIcon(),
    225                 iconColorBlack, 0);
    226 
    227         final ColorStateList emeraldColor = ResourcesCompat.getColorStateList(
    228                 mResources, R.color.color_state_list_emerald_translucent, null);
    229         final @ColorInt int emeraldDefault = ResourcesCompat.getColor(
    230                 mResources, R.color.emerald_translucent_default, null);
    231         // This is the fill color of R.drawable.test_background_red that will be set on our
    232         // menu icon that we'll be testing in this method
    233         final @ColorInt int iconColorRed = ResourcesCompat.getColor(
    234                 mResources, R.color.test_red, null);
    235 
    236         // Set up the tinting of our menu item. The tint list is using translucent color, and the
    237         // tint mode is going to be src_over, which will create a "mix" of the original icon with
    238         // the translucent tint color.
    239         MenuItemCompat.setIconTintList(secondItem, emeraldColor);
    240         MenuItemCompat.setIconTintMode(secondItem, PorterDuff.Mode.SRC_OVER);
    241 
    242         // From this point on in this method we're allowing a margin of error in checking the
    243         // color of the menu icon. This is due to both translucent colors being used
    244         // in the color state list and off-by-one discrepancies of SRC_OVER when it's compositing
    245         // translucent color on top of solid fill color. This is where the allowed variance
    246         // value of 2 comes from - one for compositing and one for color translucency.
    247         final int allowedComponentVariance = 2;
    248 
    249         // Test the tinting set up with the just loaded tint list.
    250         verifyIconIsColoredAs("Emerald tinting on green icon",
    251                 secondItem.getIcon(), ColorUtils.compositeColors(emeraldDefault, iconColorBlack),
    252                 allowedComponentVariance);
    253 
    254         // Set a different icon on our menu item
    255         secondItem.setIcon(R.drawable.test_drawable_red);
    256 
    257         // Test the tinting of the new menu icon with the same color state list
    258         verifyIconIsColoredAs("Emerald tinting on red icon",
    259                 secondItem.getIcon(), ColorUtils.compositeColors(emeraldDefault, iconColorRed),
    260                 allowedComponentVariance);
    261     }
    262 }
    263