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