/* * 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 androidx.appcompat.app; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.support.test.annotation.UiThreadTest; import android.support.test.filters.SmallTest; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; import android.view.Menu; import android.view.MenuItem; import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.appcompat.test.R; import androidx.appcompat.testutils.TestUtils; import androidx.core.content.res.ResourcesCompat; import androidx.core.graphics.ColorUtils; import androidx.core.view.MenuItemCompat; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; /** * Test icon tinting in {@link MenuItem}s */ @SmallTest @RunWith(AndroidJUnit4.class) public class AppCompatMenuItemIconTintingTest { private AppCompatMenuItemIconTintingTestActivity mActivity; private Resources mResources; private Menu mMenu; @Rule public ActivityTestRule mActivityTestRule = new ActivityTestRule<>(AppCompatMenuItemIconTintingTestActivity.class); @Before public void setup() { mActivity = mActivityTestRule.getActivity(); mResources = mActivity.getResources(); mMenu = mActivity.getToolbarMenu(); } @UiThreadTest @Test public void testIconTinting() throws Throwable { final MenuItem firstItem = mMenu.getItem(0); final MenuItem secondItem = mMenu.getItem(1); final MenuItem thirdItem = mMenu.getItem(2); // These are the default set in layout XML assertNull(MenuItemCompat.getIconTintMode(firstItem)); assertEquals(Color.WHITE, MenuItemCompat.getIconTintList(firstItem).getDefaultColor()); assertEquals(PorterDuff.Mode.SCREEN, MenuItemCompat.getIconTintMode(secondItem)); assertNull(MenuItemCompat.getIconTintList(secondItem)); assertNull(MenuItemCompat.getIconTintMode(thirdItem)); assertNull(MenuItemCompat.getIconTintList(thirdItem)); // Change tint color list and mode and verify that they are returned by the getters final ColorStateList colors = ColorStateList.valueOf(Color.RED); MenuItemCompat.setIconTintList(firstItem, colors); MenuItemCompat.setIconTintMode(firstItem, PorterDuff.Mode.XOR); assertSame(colors, MenuItemCompat.getIconTintList(firstItem)); assertEquals(PorterDuff.Mode.XOR, MenuItemCompat.getIconTintMode(firstItem)); // Ensure the tint is preserved across drawable changes. firstItem.setIcon(R.drawable.icon_yellow); assertSame(colors, MenuItemCompat.getIconTintList(firstItem)); assertEquals(PorterDuff.Mode.XOR, MenuItemCompat.getIconTintMode(firstItem)); // Change tint color list and mode again and verify that they are returned by the getters final ColorStateList colorsNew = ColorStateList.valueOf(Color.MAGENTA); MenuItemCompat.setIconTintList(firstItem, colorsNew); MenuItemCompat.setIconTintMode(firstItem, PorterDuff.Mode.SRC_IN); assertSame(colorsNew, MenuItemCompat.getIconTintList(firstItem)); assertEquals(PorterDuff.Mode.SRC_IN, MenuItemCompat.getIconTintMode(firstItem)); } private void verifyIconIsColoredAs(String description, @NonNull Drawable icon, @ColorInt int color, int allowedComponentVariance) { TestUtils.assertAllPixelsOfColor(description, icon, icon.getIntrinsicWidth(), icon.getIntrinsicHeight(), true, color, allowedComponentVariance, false); } /** * This method tests that icon tinting is not applied when the * menu item has no icon. */ @UiThreadTest @Test public void testIconTintingWithNoIcon() { final MenuItem sixthItem = mMenu.getItem(5); // Note that all the asserts in this test check that the menu item icon // is null. This is because the matching entry in the XML doesn't define any // icon, and there is nothing to tint. assertNull("No icon after XML loading", sixthItem.getIcon()); // Load a new color state list, set it on the menu item icon and check that the icon // is still null. final ColorStateList sandColor = ResourcesCompat.getColorStateList( mResources, R.color.color_state_list_sand, null); MenuItemCompat.setIconTintList(sixthItem, sandColor); assertNull("No icon after setting icon tint list", sixthItem.getIcon()); // Set tint mode on the menu item icon and check that the icon is still null. MenuItemCompat.setIconTintMode(sixthItem, PorterDuff.Mode.MULTIPLY); assertNull("No icon after setting icon tint mode", sixthItem.getIcon()); } /** * This method tests that icon tinting is applied across a number of * ColorStateLists set as icon tint lists on the same menu item. */ @UiThreadTest @Test public void testIconTintingAcrossTintListChange() { final MenuItem firstItem = mMenu.getItem(0); final @ColorInt int sandDefault = ResourcesCompat.getColor( mResources, R.color.sand_default, null); final @ColorInt int oceanDefault = ResourcesCompat.getColor( mResources, R.color.ocean_default, null); // Test the default state for tinting set up in the menu XML file. verifyIconIsColoredAs("Default white tinting", firstItem.getIcon(), Color.WHITE, 0); // Load a new color state list, set it on the menu item and check that the icon has // switched to the matching entry in newly set color state list. final ColorStateList sandColor = ResourcesCompat.getColorStateList( mResources, R.color.color_state_list_sand, null); MenuItemCompat.setIconTintList(firstItem, sandColor); verifyIconIsColoredAs("Default white tinting", firstItem.getIcon(), sandDefault, 0); // Load another color state list, set it on the menu item and check that the icon has // switched to the matching entry in newly set color state list. final ColorStateList oceanColor = ResourcesCompat.getColorStateList( mResources, R.color.color_state_list_ocean, null); MenuItemCompat.setIconTintList(firstItem, oceanColor); verifyIconIsColoredAs("Default white tinting", firstItem.getIcon(), oceanDefault, 0); } /** * This method tests that opaque icon tinting is applied correctly after changing the icon * itself of the menu item. */ @UiThreadTest @Test public void testIconOpaqueTintingAcrossIconChange() { final MenuItem secondItem = mMenu.getItem(1); // This is the fill color of R.drawable.icon_black set on our menu icon // that we'll be testing in this method final @ColorInt int iconColorBlack = 0xFF000000; // At this point we shouldn't have any tinting since it's not defined in the menu XML verifyIconIsColoredAs("Black icon before any tinting", secondItem.getIcon(), iconColorBlack, 0); // Now set up the tinting final ColorStateList lilacColor = ResourcesCompat.getColorStateList( mResources, R.color.color_state_list_lilac, null); final @ColorInt int lilacDefault = ResourcesCompat.getColor( mResources, R.color.lilac_default, null); MenuItemCompat.setIconTintList(secondItem, lilacColor); MenuItemCompat.setIconTintMode(secondItem, PorterDuff.Mode.SRC_OVER); // Check that the icon is now tinted verifyIconIsColoredAs("Lilac icon after tinting the black icon", secondItem.getIcon(), lilacDefault, 0); // Set a different icon on our menu item secondItem.setIcon(R.drawable.test_drawable_red); // Check that the icon is still tinted with the same color as before verifyIconIsColoredAs("Lilac icon after changing icon to red", secondItem.getIcon(), lilacDefault, 0); } /** * This method tests that translucent icon tinting is applied correctly after changing the icon * itself of the menu item. */ @UiThreadTest @Test public void testIconTranslucentTintingAcrossIconChange() { final MenuItem secondItem = mMenu.getItem(1); // This is the fill color of R.drawable.icon_black set on our menu icon // that we'll be testing in this method final @ColorInt int iconColorBlack = 0xFF000000; // At this point we shouldn't have any tinting since it's not defined in the menu XML verifyIconIsColoredAs("Black icon before any tinting", secondItem.getIcon(), iconColorBlack, 0); final ColorStateList emeraldColor = ResourcesCompat.getColorStateList( mResources, R.color.color_state_list_emerald_translucent, null); final @ColorInt int emeraldDefault = ResourcesCompat.getColor( mResources, R.color.emerald_translucent_default, null); // This is the fill color of R.drawable.test_background_red that will be set on our // menu icon that we'll be testing in this method final @ColorInt int iconColorRed = ResourcesCompat.getColor( mResources, R.color.test_red, null); // Set up the tinting of our menu item. The tint list is using translucent color, and the // tint mode is going to be src_over, which will create a "mix" of the original icon with // the translucent tint color. MenuItemCompat.setIconTintList(secondItem, emeraldColor); MenuItemCompat.setIconTintMode(secondItem, PorterDuff.Mode.SRC_OVER); // From this point on in this method we're allowing a margin of error in checking the // color of the menu icon. 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 tinting set up with the just loaded tint list. verifyIconIsColoredAs("Emerald tinting on green icon", secondItem.getIcon(), ColorUtils.compositeColors(emeraldDefault, iconColorBlack), allowedComponentVariance); // Set a different icon on our menu item secondItem.setIcon(R.drawable.test_drawable_red); // Test the tinting of the new menu icon with the same color state list verifyIconIsColoredAs("Emerald tinting on red icon", secondItem.getIcon(), ColorUtils.compositeColors(emeraldDefault, iconColorRed), allowedComponentVariance); } }