1/*
2 * Copyright (C) 2016 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 */
16package android.support.v7.widget;
17
18import android.content.res.ColorStateList;
19import android.content.res.Resources;
20import android.graphics.PorterDuff;
21import android.graphics.drawable.Drawable;
22import android.support.annotation.ColorInt;
23import android.support.annotation.IdRes;
24import android.support.annotation.NonNull;
25import android.support.v4.content.res.ResourcesCompat;
26import android.support.v4.graphics.ColorUtils;
27import android.support.v7.app.BaseInstrumentationTestCase;
28import android.support.v7.appcompat.test.R;
29import android.support.v7.testutils.AppCompatTintableViewActions;
30import android.support.v7.testutils.BaseTestActivity;
31import android.support.v7.testutils.TestUtils;
32import android.support.v7.testutils.TestUtilsActions;
33import android.test.suitebuilder.annotation.SmallTest;
34import android.view.View;
35import android.view.ViewGroup;
36import org.junit.Before;
37import org.junit.Test;
38
39import static android.support.test.espresso.Espresso.onView;
40import static android.support.test.espresso.matcher.ViewMatchers.withId;
41import static org.junit.Assert.assertNull;
42
43/**
44 * Base class for testing custom view extensions in appcompat-v7 that implement the
45 * <code>TintableBackgroundView</code> interface. Extensions of this class run all tests
46 * from here and add test cases specific to the functionality they add to the relevant
47 * base view class (such as <code>AppCompatTextView</code>'s all-caps support).
48 */
49public abstract class AppCompatBaseViewTest<A extends BaseTestActivity, T extends View>
50        extends BaseInstrumentationTestCase<A> {
51    protected ViewGroup mContainer;
52
53    protected Resources mResources;
54
55    public AppCompatBaseViewTest(Class clazz) {
56        super(clazz);
57    }
58
59    @Before
60    public void setUp() {
61        final A activity = mActivityTestRule.getActivity();
62        mContainer = (ViewGroup) activity.findViewById(R.id.container);
63        mResources = activity.getResources();
64    }
65
66    /**
67     * Subclasses should override this method to return true if by default the matching
68     * view (such as, say, {@link AppCompatSpinner}) has background set it.
69     */
70    protected boolean hasBackgroundByDefault() {
71        return false;
72    }
73
74    private void verifyBackgroundIsColoredAs(String description, @NonNull View view,
75            @ColorInt int color, int allowedComponentVariance) {
76        Drawable background = view.getBackground();
77        TestUtils.assertAllPixelsOfColor(description,
78                background, view.getWidth(), view.getHeight(), true,
79                color, allowedComponentVariance, false);
80    }
81
82    /**
83     * This method tests that background tinting is not applied when the
84     * tintable view has no background.
85     */
86    @Test
87    @SmallTest
88    public void testBackgroundTintingWithNoBackground() {
89        if (hasBackgroundByDefault()) {
90            return;
91        }
92
93        final @IdRes int viewId = R.id.view_tinted_no_background;
94        final T view = (T) mContainer.findViewById(viewId);
95
96        // Note that all the asserts in this test check that the view background
97        // is null. This is because the matching child in the activity doesn't define any
98        // background on itself, and there is nothing to tint.
99
100        assertNull("No background after XML loading", view.getBackground());
101
102        // Disable the view and check that the background is still null.
103        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
104        assertNull("No background after disabling", view.getBackground());
105
106        // Enable the view and check that the background is still null.
107        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
108        assertNull("No background after re-enabling", view.getBackground());
109
110        // Load a new color state list, set it on the view and check that the background
111        // is still null.
112        final ColorStateList sandColor = ResourcesCompat.getColorStateList(
113                mResources, R.color.color_state_list_sand, null);
114        onView(withId(viewId)).perform(
115                AppCompatTintableViewActions.setBackgroundTintList(sandColor));
116
117        // Disable the view and check that the background is still null.
118        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
119        assertNull("No background after disabling", view.getBackground());
120
121        // Enable the view and check that the background is still null.
122        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
123        assertNull("No background after re-enabling", view.getBackground());
124    }
125
126    /**
127     * This method tests that background tinting is not applied when the
128     * tintable view has no background.
129     */
130    @Test
131    @SmallTest
132    public void testBackgroundTintingViewCompatWithNoBackground() {
133        if (hasBackgroundByDefault()) {
134            return;
135        }
136
137        final @IdRes int viewId = R.id.view_tinted_no_background;
138        final T view = (T) mContainer.findViewById(viewId);
139
140        // Note that all the asserts in this test check that the view background
141        // is null. This is because the matching child in the activity doesn't define any
142        // background on itself, and there is nothing to tint.
143
144        assertNull("No background after XML loading", view.getBackground());
145
146        // Disable the view and check that the background is still null.
147        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
148        assertNull("No background after disabling", view.getBackground());
149
150        // Enable the view and check that the background is still null.
151        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
152        assertNull("No background after re-enabling", view.getBackground());
153
154        // Load a new color state list, set it on the view and check that the background
155        // is still null.
156        final ColorStateList lilacColor = ResourcesCompat.getColorStateList(
157                mResources, R.color.color_state_list_lilac, null);
158        onView(withId(viewId)).perform(
159                TestUtilsActions.setBackgroundTintListViewCompat(lilacColor));
160
161        // Disable the view and check that the background is still null.
162        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
163        assertNull("No background after disabling", view.getBackground());
164
165        // Enable the view and check that the background is still null.
166        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
167        assertNull("No background after re-enabling", view.getBackground());
168    }
169
170    /**
171     * This method tests that background tinting is applied to tintable view
172     * in enabled and disabled state across a number of <code>ColorStateList</code>s set as
173     * background tint lists on the same background.
174     */
175    @Test
176    @SmallTest
177    public void testBackgroundTintingAcrossStateChange() {
178        final @IdRes int viewId = R.id.view_tinted_background;
179        final T view = (T) mContainer.findViewById(viewId);
180
181        final @ColorInt int lilacDefault = ResourcesCompat.getColor(
182                mResources, R.color.lilac_default, null);
183        final @ColorInt int lilacDisabled = ResourcesCompat.getColor(
184                mResources, R.color.lilac_disabled, null);
185        final @ColorInt int sandDefault = ResourcesCompat.getColor(
186                mResources, R.color.sand_default, null);
187        final @ColorInt int sandDisabled = ResourcesCompat.getColor(
188                mResources, R.color.sand_disabled, null);
189        final @ColorInt int oceanDefault = ResourcesCompat.getColor(
190                mResources, R.color.ocean_default, null);
191        final @ColorInt int oceanDisabled = ResourcesCompat.getColor(
192                mResources, R.color.ocean_disabled, null);
193
194        // Test the default state for tinting set up in the layout XML file.
195        verifyBackgroundIsColoredAs("Default lilac tinting in enabled state", view,
196                lilacDefault, 0);
197
198        // Disable the view and check that the background has switched to the matching entry
199        // in the default color state list.
200        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
201        verifyBackgroundIsColoredAs("Default lilac tinting in disabled state", view,
202                lilacDisabled, 0);
203
204        // Enable the view and check that the background has switched to the matching entry
205        // in the default color state list.
206        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
207        verifyBackgroundIsColoredAs("Default lilac tinting in re-enabled state", view,
208                lilacDefault, 0);
209
210        // Load a new color state list, set it on the view and check that the background has
211        // switched to the matching entry in newly set color state list.
212        final ColorStateList sandColor = ResourcesCompat.getColorStateList(
213                mResources, R.color.color_state_list_sand, null);
214        onView(withId(viewId)).perform(
215                AppCompatTintableViewActions.setBackgroundTintList(sandColor));
216        verifyBackgroundIsColoredAs("New sand tinting in enabled state", view,
217                sandDefault, 0);
218
219        // Disable the view and check that the background has switched to the matching entry
220        // in the newly set color state list.
221        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
222        verifyBackgroundIsColoredAs("New sand tinting in disabled state", view,
223                sandDisabled, 0);
224
225        // Enable the view and check that the background has switched to the matching entry
226        // in the newly set color state list.
227        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
228        verifyBackgroundIsColoredAs("New sand tinting in re-enabled state", view,
229                sandDefault, 0);
230
231        // Load another color state list, set it on the view and check that the background has
232        // switched to the matching entry in newly set color state list.
233        final ColorStateList oceanColor = ResourcesCompat.getColorStateList(
234                mResources, R.color.color_state_list_ocean, null);
235        onView(withId(viewId)).perform(
236                AppCompatTintableViewActions.setBackgroundTintList(oceanColor));
237        verifyBackgroundIsColoredAs("New ocean tinting in enabled state", view,
238                oceanDefault, 0);
239
240        // Disable the view and check that the background has switched to the matching entry
241        // in the newly set color state list.
242        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
243        verifyBackgroundIsColoredAs("New ocean tinting in disabled state", view,
244                oceanDisabled, 0);
245
246        // Enable the view and check that the background has switched to the matching entry
247        // in the newly set color state list.
248        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
249        verifyBackgroundIsColoredAs("New ocean tinting in re-enabled state", view,
250                oceanDefault, 0);
251    }
252
253    /**
254     * This method tests that background tinting is applied to tintable view
255     * in enabled and disabled state across a number of <code>ColorStateList</code>s set as
256     * background tint lists on the same background.
257     */
258    @Test
259    @SmallTest
260    public void testBackgroundTintingViewCompatAcrossStateChange() {
261        final @IdRes int viewId = R.id.view_tinted_background;
262        final T view = (T) mContainer.findViewById(viewId);
263
264        final @ColorInt int lilacDefault = ResourcesCompat.getColor(
265                mResources, R.color.lilac_default, null);
266        final @ColorInt int lilacDisabled = ResourcesCompat.getColor(
267                mResources, R.color.lilac_disabled, null);
268        final @ColorInt int sandDefault = ResourcesCompat.getColor(
269                mResources, R.color.sand_default, null);
270        final @ColorInt int sandDisabled = ResourcesCompat.getColor(
271                mResources, R.color.sand_disabled, null);
272        final @ColorInt int oceanDefault = ResourcesCompat.getColor(
273                mResources, R.color.ocean_default, null);
274        final @ColorInt int oceanDisabled = ResourcesCompat.getColor(
275                mResources, R.color.ocean_disabled, null);
276
277        // Test the default state for tinting set up in the layout XML file.
278        verifyBackgroundIsColoredAs("Default lilac tinting in enabled state", view,
279                lilacDefault, 0);
280
281        // Disable the view and check that the background has switched to the matching entry
282        // in the default color state list.
283        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
284        verifyBackgroundIsColoredAs("Default lilac tinting in disabled state", view,
285                lilacDisabled, 0);
286
287        // Enable the view and check that the background has switched to the matching entry
288        // in the default color state list.
289        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
290        verifyBackgroundIsColoredAs("Default lilac tinting in re-enabled state", view,
291                lilacDefault, 0);
292
293        // Load a new color state list, set it on the view and check that the background has
294        // switched to the matching entry in newly set color state list.
295        final ColorStateList sandColor = ResourcesCompat.getColorStateList(
296                mResources, R.color.color_state_list_sand, null);
297        onView(withId(viewId)).perform(
298                TestUtilsActions.setBackgroundTintListViewCompat(sandColor));
299        verifyBackgroundIsColoredAs("New sand tinting in enabled state", view,
300                sandDefault, 0);
301
302        // Disable the view and check that the background has switched to the matching entry
303        // in the newly set color state list.
304        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
305        verifyBackgroundIsColoredAs("New sand tinting in disabled state", view,
306                sandDisabled, 0);
307
308        // Enable the view and check that the background has switched to the matching entry
309        // in the newly set color state list.
310        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
311        verifyBackgroundIsColoredAs("New sand tinting in re-enabled state", view,
312                sandDefault, 0);
313
314        // Load another color state list, set it on the view and check that the background has
315        // switched to the matching entry in newly set color state list.
316        final ColorStateList oceanColor = ResourcesCompat.getColorStateList(
317                mResources, R.color.color_state_list_ocean, null);
318        onView(withId(viewId)).perform(
319                TestUtilsActions.setBackgroundTintListViewCompat(oceanColor));
320        verifyBackgroundIsColoredAs("New ocean tinting in enabled state", view,
321                oceanDefault, 0);
322
323        // Disable the view and check that the background has switched to the matching entry
324        // in the newly set color state list.
325        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
326        verifyBackgroundIsColoredAs("New ocean tinting in disabled state", view,
327                oceanDisabled, 0);
328
329        // Enable the view and check that the background has switched to the matching entry
330        // in the newly set color state list.
331        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
332        verifyBackgroundIsColoredAs("New ocean tinting in re-enabled state", view,
333                oceanDefault, 0);
334    }
335
336    /**
337     * This method tests that background tinting applied to tintable view
338     * in enabled and disabled state across the same background respects the currently set
339     * background tinting mode.
340     */
341    @Test
342    @SmallTest
343    public void testBackgroundTintingAcrossModeChange() {
344        final @IdRes int viewId = R.id.view_untinted_background;
345        final T view = (T) mContainer.findViewById(viewId);
346
347        final @ColorInt int emeraldDefault = ResourcesCompat.getColor(
348                mResources, R.color.emerald_translucent_default, null);
349        final @ColorInt int emeraldDisabled = ResourcesCompat.getColor(
350                mResources, R.color.emerald_translucent_disabled, null);
351        // This is the fill color of R.drawable.test_background_green set on our view
352        // that we'll be testing in this method
353        final @ColorInt int backgroundColor = ResourcesCompat.getColor(
354                mResources, R.color.test_green, null);
355
356        // Test the default state for tinting set up in the layout XML file.
357        verifyBackgroundIsColoredAs("Default no tinting in enabled state", view,
358                backgroundColor, 0);
359
360        // From this point on in this method we're allowing a margin of error in checking the
361        // color of the view background. This is due to both translucent colors being used
362        // in the color state list and off-by-one discrepancies of SRC_OVER when it's compositing
363        // translucent color on top of solid fill color. This is where the allowed variance
364        // value of 2 comes from - one for compositing and one for color translucency.
365        final int allowedComponentVariance = 2;
366
367        // Set src_in tint mode on our view
368        onView(withId(viewId)).perform(
369                AppCompatTintableViewActions.setBackgroundTintMode(PorterDuff.Mode.SRC_IN));
370
371        // Load a new color state list, set it on the view and check that the background has
372        // switched to the matching entry in newly set color state list.
373        final ColorStateList emeraldColor = ResourcesCompat.getColorStateList(
374                mResources, R.color.color_state_list_emerald_translucent, null);
375        onView(withId(viewId)).perform(
376                AppCompatTintableViewActions.setBackgroundTintList(emeraldColor));
377        verifyBackgroundIsColoredAs("New emerald tinting in enabled state under src_in", view,
378                emeraldDefault, allowedComponentVariance);
379
380        // Disable the view and check that the background has switched to the matching entry
381        // in the newly set color state list.
382        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
383        verifyBackgroundIsColoredAs("New emerald tinting in disabled state under src_in", view,
384                emeraldDisabled, allowedComponentVariance);
385
386        // Set src_over tint mode on our view. As the currently set tint list is using
387        // translucent colors, we expect the actual background of the view to be different under
388        // this new mode (unlike src_in and src_over that behave identically when the destination is
389        // a fully filled rectangle and the source is an opaque color).
390        onView(withId(viewId)).perform(
391                AppCompatTintableViewActions.setBackgroundTintMode(PorterDuff.Mode.SRC_OVER));
392
393        // Enable the view and check that the background has switched to the matching entry
394        // in the color state list.
395        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
396        verifyBackgroundIsColoredAs("New emerald tinting in enabled state under src_over", view,
397                ColorUtils.compositeColors(emeraldDefault, backgroundColor),
398                allowedComponentVariance);
399
400        // Disable the view and check that the background has switched to the matching entry
401        // in the newly set color state list.
402        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
403        verifyBackgroundIsColoredAs("New emerald tinting in disabled state under src_over",
404                view, ColorUtils.compositeColors(emeraldDisabled, backgroundColor),
405                allowedComponentVariance);
406    }
407
408    /**
409     * This method tests that background tinting applied to tintable view
410     * in enabled and disabled state across the same background respects the currently set
411     * background tinting mode.
412     */
413    @Test
414    @SmallTest
415    public void testBackgroundTintingViewCompatAcrossModeChange() {
416        final @IdRes int viewId = R.id.view_untinted_background;
417        final T view = (T) mContainer.findViewById(viewId);
418
419        final @ColorInt int emeraldDefault = ResourcesCompat.getColor(
420                mResources, R.color.emerald_translucent_default, null);
421        final @ColorInt int emeraldDisabled = ResourcesCompat.getColor(
422                mResources, R.color.emerald_translucent_disabled, null);
423        // This is the fill color of R.drawable.test_background_green set on our view
424        // that we'll be testing in this method
425        final @ColorInt int backgroundColor = ResourcesCompat.getColor(
426                mResources, R.color.test_green, null);
427
428        // Test the default state for tinting set up in the layout XML file.
429        verifyBackgroundIsColoredAs("Default no tinting in enabled state", view,
430                backgroundColor, 0);
431
432        // From this point on in this method we're allowing a margin of error in checking the
433        // color of the view background. This is due to both translucent colors being used
434        // in the color state list and off-by-one discrepancies of SRC_OVER when it's compositing
435        // translucent color on top of solid fill color. This is where the allowed variance
436        // value of 2 comes from - one for compositing and one for color translucency.
437        final int allowedComponentVariance = 2;
438
439        // Set src_in tint mode on our view
440        onView(withId(viewId)).perform(
441                TestUtilsActions.setBackgroundTintModeViewCompat(PorterDuff.Mode.SRC_IN));
442
443        // Load a new color state list, set it on the view and check that the background has
444        // switched to the matching entry in newly set color state list.
445        final ColorStateList emeraldColor = ResourcesCompat.getColorStateList(
446                mResources, R.color.color_state_list_emerald_translucent, null);
447        onView(withId(viewId)).perform(
448                TestUtilsActions.setBackgroundTintListViewCompat(emeraldColor));
449        verifyBackgroundIsColoredAs("New emerald tinting in enabled state under src_in", view,
450                emeraldDefault, allowedComponentVariance);
451
452        // Disable the view and check that the background has switched to the matching entry
453        // in the newly set color state list.
454        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
455        verifyBackgroundIsColoredAs("New emerald tinting in disabled state under src_in", view,
456                emeraldDisabled, allowedComponentVariance);
457
458        // Set src_over tint mode on our view. As the currently set tint list is using
459        // translucent colors, we expect the actual background of the view to be different under
460        // this new mode (unlike src_in and src_over that behave identically when the destination is
461        // a fully filled rectangle and the source is an opaque color).
462        onView(withId(viewId)).perform(
463                TestUtilsActions.setBackgroundTintModeViewCompat(PorterDuff.Mode.SRC_OVER));
464
465        // Enable the view and check that the background has switched to the matching entry
466        // in the color state list.
467        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
468        verifyBackgroundIsColoredAs("New emerald tinting in enabled state under src_over", view,
469                ColorUtils.compositeColors(emeraldDefault, backgroundColor),
470                allowedComponentVariance);
471
472        // Disable the view and check that the background has switched to the matching entry
473        // in the newly set color state list.
474        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
475        verifyBackgroundIsColoredAs("New emerald tinting in disabled state under src_over",
476                view, ColorUtils.compositeColors(emeraldDisabled, backgroundColor),
477                allowedComponentVariance);
478    }
479
480    /**
481     * This method tests that opaque background tinting applied to tintable view
482     * is applied correctly after changing the background itself of the view.
483     */
484    @Test
485    @SmallTest
486    public void testBackgroundOpaqueTintingAcrossBackgroundChange() {
487        final @IdRes int viewId = R.id.view_tinted_no_background;
488        final T view = (T) mContainer.findViewById(viewId);
489
490        final @ColorInt int lilacDefault = ResourcesCompat.getColor(
491                mResources, R.color.lilac_default, null);
492        final @ColorInt int lilacDisabled = ResourcesCompat.getColor(
493                mResources, R.color.lilac_disabled, null);
494
495        if (!hasBackgroundByDefault()) {
496            assertNull("No background after XML loading", view.getBackground());
497        }
498
499        // Set background on our view
500        onView(withId(viewId)).perform(AppCompatTintableViewActions.setBackgroundDrawable(
501                ResourcesCompat.getDrawable(mResources, R.drawable.test_background_green, null)));
502
503        // Test the default state for tinting set up in the layout XML file.
504        verifyBackgroundIsColoredAs("Default lilac tinting in enabled state on green background",
505                view, lilacDefault, 0);
506
507        // Disable the view and check that the background has switched to the matching entry
508        // in the default color state list.
509        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
510        verifyBackgroundIsColoredAs("Default lilac tinting in disabled state on green background",
511                view, lilacDisabled, 0);
512
513        // Enable the view and check that the background has switched to the matching entry
514        // in the default color state list.
515        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
516        verifyBackgroundIsColoredAs("Default lilac tinting in re-enabled state on green background",
517                view, lilacDefault, 0);
518
519        // Set a different background on our view based on resource ID
520        onView(withId(viewId)).perform(AppCompatTintableViewActions.setBackgroundResource(
521                R.drawable.test_background_red));
522
523        // Test the default state for tinting set up in the layout XML file.
524        verifyBackgroundIsColoredAs("Default lilac tinting in enabled state on red background",
525                view, lilacDefault, 0);
526
527        // Disable the view and check that the background has switched to the matching entry
528        // in the default color state list.
529        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
530        verifyBackgroundIsColoredAs("Default lilac tinting in disabled state on red background",
531                view, lilacDisabled, 0);
532
533        // Enable the view and check that the background has switched to the matching entry
534        // in the default color state list.
535        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
536        verifyBackgroundIsColoredAs("Default lilac tinting in re-enabled state on red background",
537                view, lilacDefault, 0);
538    }
539
540    /**
541     * This method tests that translucent background tinting applied to tintable view
542     * is applied correctly after changing the background itself of the view.
543     */
544    @Test
545    @SmallTest
546    public void testBackgroundTranslucentTintingAcrossBackgroundChange() {
547        final @IdRes int viewId = R.id.view_untinted_no_background;
548        final T view = (T) mContainer.findViewById(viewId);
549
550        final @ColorInt int emeraldDefault = ResourcesCompat.getColor(
551                mResources, R.color.emerald_translucent_default, null);
552        final @ColorInt int emeraldDisabled = ResourcesCompat.getColor(
553                mResources, R.color.emerald_translucent_disabled, null);
554        // This is the fill color of R.drawable.test_background_green set on our view
555        // that we'll be testing in this method
556        final @ColorInt int backgroundColorGreen = ResourcesCompat.getColor(
557                mResources, R.color.test_green, null);
558        final @ColorInt int backgroundColorRed = ResourcesCompat.getColor(
559                mResources, R.color.test_red, null);
560
561        if (!hasBackgroundByDefault()) {
562            assertNull("No background after XML loading", view.getBackground());
563        }
564
565        // Set src_over tint mode on our view. As the currently set tint list is using
566        // translucent colors, we expect the actual background of the view to be different under
567        // this new mode (unlike src_in and src_over that behave identically when the destination is
568        // a fully filled rectangle and the source is an opaque color).
569        onView(withId(viewId)).perform(
570                AppCompatTintableViewActions.setBackgroundTintMode(PorterDuff.Mode.SRC_OVER));
571        // Load and set a translucent color state list as the background tint list
572        final ColorStateList emeraldColor = ResourcesCompat.getColorStateList(
573                mResources, R.color.color_state_list_emerald_translucent, null);
574        onView(withId(viewId)).perform(
575                AppCompatTintableViewActions.setBackgroundTintList(emeraldColor));
576
577        // Set background on our view
578        onView(withId(viewId)).perform(AppCompatTintableViewActions.setBackgroundDrawable(
579                ResourcesCompat.getDrawable(mResources, R.drawable.test_background_green, null)));
580
581        // From this point on in this method we're allowing a margin of error in checking the
582        // color of the view background. This is due to both translucent colors being used
583        // in the color state list and off-by-one discrepancies of SRC_OVER when it's compositing
584        // translucent color on top of solid fill color. This is where the allowed variance
585        // value of 2 comes from - one for compositing and one for color translucency.
586        final int allowedComponentVariance = 2;
587
588        // Test the default state for tinting set up with the just loaded tint list.
589        verifyBackgroundIsColoredAs("Emerald tinting in enabled state on green background",
590                view, ColorUtils.compositeColors(emeraldDefault, backgroundColorGreen),
591                allowedComponentVariance);
592
593        // Disable the view and check that the background has switched to the matching entry
594        // in the default color state list.
595        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
596        verifyBackgroundIsColoredAs("Emerald tinting in disabled state on green background",
597                view, ColorUtils.compositeColors(emeraldDisabled, backgroundColorGreen),
598                allowedComponentVariance);
599
600        // Enable the view and check that the background has switched to the matching entry
601        // in the default color state list.
602        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
603        verifyBackgroundIsColoredAs("Emerald tinting in re-enabled state on green background",
604                view, ColorUtils.compositeColors(emeraldDefault, backgroundColorGreen),
605                allowedComponentVariance);
606
607        // Set a different background on our view based on resource ID
608        onView(withId(viewId)).perform(AppCompatTintableViewActions.setBackgroundResource(
609                R.drawable.test_background_red));
610
611        // Test the default state for tinting the new background with the same color state list
612        verifyBackgroundIsColoredAs("Emerald tinting in enabled state on red background",
613                view, ColorUtils.compositeColors(emeraldDefault, backgroundColorRed),
614                allowedComponentVariance);
615
616        // Disable the view and check that the background has switched to the matching entry
617        // in our current color state list.
618        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
619        verifyBackgroundIsColoredAs("Emerald tinting in disabled state on red background",
620                view, ColorUtils.compositeColors(emeraldDisabled, backgroundColorRed),
621                allowedComponentVariance);
622
623        // Enable the view and check that the background has switched to the matching entry
624        // in our current color state list.
625        onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
626        verifyBackgroundIsColoredAs("Emerald tinting in re-enabled state on red background",
627                view, ColorUtils.compositeColors(emeraldDefault, backgroundColorRed),
628                allowedComponentVariance);
629    }
630}
631