1244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikov/*
2244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikov * Copyright (C) 2015 The Android Open Source Project
3244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikov *
4244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikov * Licensed under the Apache License, Version 2.0 (the "License");
5244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikov * you may not use this file except in compliance with the License.
6244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikov * You may obtain a copy of the License at
7244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikov *
8244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikov *      http://www.apache.org/licenses/LICENSE-2.0
9244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikov *
10244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikov * Unless required by applicable law or agreed to in writing, software
11244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikov * distributed under the License is distributed on an "AS IS" BASIS,
12244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikov * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikov * See the License for the specific language governing permissions and
14244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikov * limitations under the License.
15244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikov */
16244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikov
17244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikovpackage android.support.v7.testutils;
18244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikov
1998fb75eaf45370ead657c78e03d5bf7b83f67a6bChris Banesimport android.app.Instrumentation;
204c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banesimport android.content.Context;
21244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikovimport android.graphics.Bitmap;
22244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikovimport android.graphics.Canvas;
23244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikovimport android.graphics.Color;
24244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikovimport android.graphics.drawable.Drawable;
2524391daa4e5831395924e2f48df86e19294cc211Chris Banesimport android.os.SystemClock;
26244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikovimport android.support.annotation.ColorInt;
27244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikovimport android.support.annotation.NonNull;
2898fb75eaf45370ead657c78e03d5bf7b83f67a6bChris Banesimport android.support.test.InstrumentationRegistry;
293ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikovimport android.support.v4.util.Pair;
3098fb75eaf45370ead657c78e03d5bf7b83f67a6bChris Banesimport android.support.v7.app.AppCompatActivity;
3198fb75eaf45370ead657c78e03d5bf7b83f67a6bChris Banesimport android.support.v7.app.AppCompatDelegate;
324c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banesimport android.support.v7.widget.TintTypedArray;
333ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikovimport android.view.View;
343ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikovimport android.view.ViewParent;
3598fb75eaf45370ead657c78e03d5bf7b83f67a6bChris Banes
363ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikovimport junit.framework.Assert;
37244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikov
381d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikovimport java.util.ArrayList;
391d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikovimport java.util.List;
401d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov
41244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikovpublic class TestUtils {
42244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikov    /**
431d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov     * This method takes a view and returns a single bitmap that is the layered combination
441d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov     * of background drawables of this view and all its ancestors. It can be used to abstract
451d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov     * away the specific implementation of a view hierarchy that is not exposed via class APIs
461d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov     * or a view hierarchy that depends on the platform version. Instead of hard-coded lookups
471d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov     * of particular inner implementations of such a view hierarchy that can break during
481d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov     * refactoring or on newer platform versions, calling this API returns a "combined" background
491d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov     * of the view.
501d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov     *
511d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov     * For example, it is useful to get the combined background of a popup / dropdown without
521d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov     * delving into the inner implementation details of how that popup is implemented on a
531d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov     * particular platform version.
541d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov     */
551d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov    public static Bitmap getCombinedBackgroundBitmap(View view) {
561d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov        final int bitmapWidth = view.getWidth();
571d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov        final int bitmapHeight = view.getHeight();
581d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov
591d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov        // Create a bitmap
601d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov        final Bitmap bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight,
611d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov                Bitmap.Config.ARGB_8888);
621d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov        // Create a canvas that wraps the bitmap
631d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov        final Canvas canvas = new Canvas(bitmap);
641d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov
651d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov        // As the draw pass starts at the top of view hierarchy, our first step is to traverse
661d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov        // the ancestor hierarchy of our view and collect a list of all ancestors with non-null
671d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov        // and visible backgrounds. At each step we're keeping track of the combined offsets
681d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov        // so that we can properly combine all of the visuals together in the next pass.
691d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov        List<View> ancestorsWithBackgrounds = new ArrayList<>();
701d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov        List<Pair<Integer, Integer>> ancestorOffsets = new ArrayList<>();
711d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov        int offsetX = 0;
721d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov        int offsetY = 0;
731d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov        while (true) {
741d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov            final Drawable backgroundDrawable = view.getBackground();
751d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov            if ((backgroundDrawable != null) && backgroundDrawable.isVisible()) {
761d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov                ancestorsWithBackgrounds.add(view);
771d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov                ancestorOffsets.add(Pair.create(offsetX, offsetY));
781d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov            }
791d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov            // Go to the parent
801d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov            ViewParent parent = view.getParent();
811d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov            if (!(parent instanceof View)) {
821d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov                // We're done traversing the ancestor chain
831d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov                break;
841d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov            }
851d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov
861d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov            // Update the offsets based on the location of current view in its parent's bounds
871d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov            offsetX += view.getLeft();
881d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov            offsetY += view.getTop();
891d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov
901d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov            view = (View) parent;
911d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov        }
921d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov
931d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov        // Now we're going to iterate over the collected ancestors in reverse order (starting from
941d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov        // the topmost ancestor) and draw their backgrounds into our combined bitmap. At each step
951d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov        // we are respecting the offsets of our original view in the coordinate system of the
961d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov        // currently drawn ancestor.
971d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov        final int layerCount = ancestorsWithBackgrounds.size();
981d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov        for (int i = layerCount - 1; i >= 0; i--) {
991d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov            View ancestor = ancestorsWithBackgrounds.get(i);
1001d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov            Pair<Integer, Integer> offsets = ancestorOffsets.get(i);
1011d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov
1021d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov            canvas.translate(offsets.first, offsets.second);
1031d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov            ancestor.getBackground().draw(canvas);
1041d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov            canvas.translate(-offsets.first, -offsets.second);
1051d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov        }
1061d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov
1071d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov        return bitmap;
1081d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov    }
1091d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov
1101d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov    /**
111244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikov     * Checks whether all the pixels in the specified drawable are of the same specified color.
112244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikov     *
113244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikov     * In case there is a color mismatch, the behavior of this method depends on the
114244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikov     * <code>throwExceptionIfFails</code> parameter. If it is <code>true</code>, this method will
115244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikov     * throw an <code>Exception</code> describing the mismatch. Otherwise this method will call
116244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikov     * <code>Assert.fail</code> with detailed description of the mismatch.
117244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikov     */
118244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikov    public static void assertAllPixelsOfColor(String failMessagePrefix, @NonNull Drawable drawable,
119ee9519c17254b5e992164ff278173c4b2c7c5fceKirill Grouchnikov            int drawableWidth, int drawableHeight, boolean callSetBounds, @ColorInt int color,
1201b81f288853d60e25e870fd522c927fd72f2efb5Kirill Grouchnikov            int allowedComponentVariance, boolean throwExceptionIfFails) {
1213ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov        // Create a bitmap
1223ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov        Bitmap bitmap = Bitmap.createBitmap(drawableWidth, drawableHeight,
1233ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov                Bitmap.Config.ARGB_8888);
1243ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov        // Create a canvas that wraps the bitmap
1253ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov        Canvas canvas = new Canvas(bitmap);
1263ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov        if (callSetBounds) {
1273ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov            // Configure the drawable to have bounds that match the passed size
1283ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov            drawable.setBounds(0, 0, drawableWidth, drawableHeight);
1293ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov        }
1303ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov        // And ask the drawable to draw itself to the canvas / bitmap
1313ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov        drawable.draw(canvas);
132244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikov
133244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikov        try {
1341d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov            assertAllPixelsOfColor(failMessagePrefix, bitmap, drawableWidth, drawableHeight, color,
1351d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov                    allowedComponentVariance, throwExceptionIfFails);
1361d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov        } finally {
1371d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov            bitmap.recycle();
1381d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov        }
1391d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov    }
1401d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov
1411d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov    /**
1421d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov     * Checks whether all the pixels in the specified bitmap are of the same specified color.
1431d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov     *
1441d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov     * In case there is a color mismatch, the behavior of this method depends on the
1451d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov     * <code>throwExceptionIfFails</code> parameter. If it is <code>true</code>, this method will
1461d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov     * throw an <code>Exception</code> describing the mismatch. Otherwise this method will call
1471d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov     * <code>Assert.fail</code> with detailed description of the mismatch.
1481d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov     */
1491d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov    public static void assertAllPixelsOfColor(String failMessagePrefix, @NonNull Bitmap bitmap,
1501d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov            int bitmapWidth, int bitmapHeight, @ColorInt int color,
1511d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov            int allowedComponentVariance, boolean throwExceptionIfFails) {
1521d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov            int[] rowPixels = new int[bitmapWidth];
1531d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov        for (int row = 0; row < bitmapHeight; row++) {
1541d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov            bitmap.getPixels(rowPixels, 0, bitmapWidth, 0, row, bitmapWidth, 1);
1551d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov            for (int column = 0; column < bitmapWidth; column++) {
1563ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov                @ColorInt int colorAtCurrPixel = rowPixels[column];
1573ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov                if (!areColorsTheSameWithTolerance(color, colorAtCurrPixel,
1583ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov                        allowedComponentVariance)) {
1591d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov                    String mismatchDescription = failMessagePrefix
1603ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov                            + ": expected all drawable colors to be "
1613ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov                            + formatColorToHex(color)
1623ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov                            + " but at position (" + row + "," + column + ") out of ("
1633ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov                            + bitmapWidth + "," + bitmapHeight + ") found "
1643ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov                            + formatColorToHex(colorAtCurrPixel);
1651d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov                    if (throwExceptionIfFails) {
1661d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov                        throw new RuntimeException(mismatchDescription);
1671d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov                    } else {
1681d6e3840486930e276d142861afb6c7e72d5ce72Kirill Grouchnikov                        Assert.fail(mismatchDescription);
169244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikov                    }
170244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikov                }
171244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikov            }
172244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikov        }
173244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikov    }
17424391daa4e5831395924e2f48df86e19294cc211Chris Banes
1753ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov    /**
176c6f44e35993b51b4cb2a78222ef22d01725970ffChris Banes     * Checks whether the center pixel in the specified drawable is of the same specified color.
177c6f44e35993b51b4cb2a78222ef22d01725970ffChris Banes     *
178c6f44e35993b51b4cb2a78222ef22d01725970ffChris Banes     * In case there is a color mismatch, the behavior of this method depends on the
179c6f44e35993b51b4cb2a78222ef22d01725970ffChris Banes     * <code>throwExceptionIfFails</code> parameter. If it is <code>true</code>, this method will
180c6f44e35993b51b4cb2a78222ef22d01725970ffChris Banes     * throw an <code>Exception</code> describing the mismatch. Otherwise this method will call
181c6f44e35993b51b4cb2a78222ef22d01725970ffChris Banes     * <code>Assert.fail</code> with detailed description of the mismatch.
182c6f44e35993b51b4cb2a78222ef22d01725970ffChris Banes     */
183c6f44e35993b51b4cb2a78222ef22d01725970ffChris Banes    public static void assertCenterPixelOfColor(String failMessagePrefix, @NonNull Drawable drawable,
184c6f44e35993b51b4cb2a78222ef22d01725970ffChris Banes            int drawableWidth, int drawableHeight, boolean callSetBounds, @ColorInt int color,
185c6f44e35993b51b4cb2a78222ef22d01725970ffChris Banes            int allowedComponentVariance, boolean throwExceptionIfFails) {
186c6f44e35993b51b4cb2a78222ef22d01725970ffChris Banes        // Create a bitmap
187c6f44e35993b51b4cb2a78222ef22d01725970ffChris Banes        Bitmap bitmap = Bitmap.createBitmap(drawableWidth, drawableHeight, Bitmap.Config.ARGB_8888);
188c6f44e35993b51b4cb2a78222ef22d01725970ffChris Banes        // Create a canvas that wraps the bitmap
189c6f44e35993b51b4cb2a78222ef22d01725970ffChris Banes        Canvas canvas = new Canvas(bitmap);
190c6f44e35993b51b4cb2a78222ef22d01725970ffChris Banes        if (callSetBounds) {
191c6f44e35993b51b4cb2a78222ef22d01725970ffChris Banes            // Configure the drawable to have bounds that match the passed size
192c6f44e35993b51b4cb2a78222ef22d01725970ffChris Banes            drawable.setBounds(0, 0, drawableWidth, drawableHeight);
193c6f44e35993b51b4cb2a78222ef22d01725970ffChris Banes        }
194c6f44e35993b51b4cb2a78222ef22d01725970ffChris Banes        // And ask the drawable to draw itself to the canvas / bitmap
195c6f44e35993b51b4cb2a78222ef22d01725970ffChris Banes        drawable.draw(canvas);
196c6f44e35993b51b4cb2a78222ef22d01725970ffChris Banes
197c6f44e35993b51b4cb2a78222ef22d01725970ffChris Banes        try {
198c6f44e35993b51b4cb2a78222ef22d01725970ffChris Banes            assertCenterPixelOfColor(failMessagePrefix, bitmap, color, allowedComponentVariance,
199c6f44e35993b51b4cb2a78222ef22d01725970ffChris Banes                    throwExceptionIfFails);
200c6f44e35993b51b4cb2a78222ef22d01725970ffChris Banes        } finally {
201c6f44e35993b51b4cb2a78222ef22d01725970ffChris Banes            bitmap.recycle();
202c6f44e35993b51b4cb2a78222ef22d01725970ffChris Banes        }
203c6f44e35993b51b4cb2a78222ef22d01725970ffChris Banes    }
204c6f44e35993b51b4cb2a78222ef22d01725970ffChris Banes
205c6f44e35993b51b4cb2a78222ef22d01725970ffChris Banes    /**
2063ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov     * Checks whether the center pixel in the specified bitmap is of the same specified color.
2073ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov     *
2083ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov     * In case there is a color mismatch, the behavior of this method depends on the
2093ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov     * <code>throwExceptionIfFails</code> parameter. If it is <code>true</code>, this method will
2103ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov     * throw an <code>Exception</code> describing the mismatch. Otherwise this method will call
2113ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov     * <code>Assert.fail</code> with detailed description of the mismatch.
2123ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov     */
2133ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov    public static void assertCenterPixelOfColor(String failMessagePrefix, @NonNull Bitmap bitmap,
214c6f44e35993b51b4cb2a78222ef22d01725970ffChris Banes            @ColorInt int color, int allowedComponentVariance, boolean throwExceptionIfFails) {
2153ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov        final int centerX = bitmap.getWidth() / 2;
2163ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov        final int centerY = bitmap.getHeight() / 2;
2173ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov        final @ColorInt int colorAtCenterPixel = bitmap.getPixel(centerX, centerY);
2183ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov        if (!areColorsTheSameWithTolerance(color, colorAtCenterPixel,
2193ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov                allowedComponentVariance)) {
2203ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov            String mismatchDescription = failMessagePrefix
2213ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov                    + ": expected all drawable colors to be "
2223ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov                    + formatColorToHex(color)
2233ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov                    + " but at position (" + centerX + "," + centerY + ") out of ("
2243ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov                    + bitmap.getWidth() + "," + bitmap.getHeight() + ") found"
2253ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov                    + formatColorToHex(colorAtCenterPixel);
2263ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov            if (throwExceptionIfFails) {
2273ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov                throw new RuntimeException(mismatchDescription);
2283ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov            } else {
2293ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov                Assert.fail(mismatchDescription);
2303ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov            }
2313ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov        }
2323ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov    }
2333ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov
2343ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov    /**
2353ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov     * Formats the passed integer-packed color into the #AARRGGBB format.
2363ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov     */
2373ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov    private static String formatColorToHex(@ColorInt int color) {
2383ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov        return String.format("#%08X", (0xFFFFFFFF & color));
2393ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov    }
2403ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov
2413ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov    /**
2423ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov     * Compares two integer-packed colors to be equal, each component within the specified
2433ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov     * allowed variance. Returns <code>true</code> if the two colors are sufficiently equal
2443ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov     * and <code>false</code> otherwise.
2453ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov     */
2463ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov    private static boolean areColorsTheSameWithTolerance(@ColorInt int expectedColor,
2473ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov            @ColorInt int actualColor, int allowedComponentVariance) {
2483ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov        int sourceAlpha = Color.alpha(actualColor);
2493ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov        int sourceRed = Color.red(actualColor);
2503ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov        int sourceGreen = Color.green(actualColor);
2513ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov        int sourceBlue = Color.blue(actualColor);
2523ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov
2533ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov        int expectedAlpha = Color.alpha(expectedColor);
2543ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov        int expectedRed = Color.red(expectedColor);
2553ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov        int expectedGreen = Color.green(expectedColor);
2563ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov        int expectedBlue = Color.blue(expectedColor);
2573ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov
2583ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov        int varianceAlpha = Math.abs(sourceAlpha - expectedAlpha);
2593ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov        int varianceRed = Math.abs(sourceRed - expectedRed);
2603ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov        int varianceGreen = Math.abs(sourceGreen - expectedGreen);
2613ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov        int varianceBlue = Math.abs(sourceBlue - expectedBlue);
2623ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov
2633ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov        boolean isColorMatch = (varianceAlpha <= allowedComponentVariance)
2643ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov                && (varianceRed <= allowedComponentVariance)
2653ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov                && (varianceGreen <= allowedComponentVariance)
2663ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov                && (varianceBlue <= allowedComponentVariance);
2673ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov
2683ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov        return isColorMatch;
2693ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov    }
2703ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikov
27124391daa4e5831395924e2f48df86e19294cc211Chris Banes    public static void waitForActivityDestroyed(BaseTestActivity activity) {
27224391daa4e5831395924e2f48df86e19294cc211Chris Banes        while (!activity.isDestroyed()) {
27324391daa4e5831395924e2f48df86e19294cc211Chris Banes            SystemClock.sleep(30);
27424391daa4e5831395924e2f48df86e19294cc211Chris Banes        }
27524391daa4e5831395924e2f48df86e19294cc211Chris Banes    }
2764c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banes
2774c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banes    public static int getThemeAttrColor(Context context, int attr) {
2784c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banes        final int[] attrs = { attr };
2794c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banes        TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, null, attrs);
2804c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banes        try {
2814c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banes            return a.getColor(0, 0);
2824c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banes        } finally {
2834c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banes            a.recycle();
2844c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banes        }
2854c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banes    }
28698fb75eaf45370ead657c78e03d5bf7b83f67a6bChris Banes
28798fb75eaf45370ead657c78e03d5bf7b83f67a6bChris Banes    public static void setLocalNightModeAndWaitForRecreate(final AppCompatActivity activity,
28898fb75eaf45370ead657c78e03d5bf7b83f67a6bChris Banes            @AppCompatDelegate.NightMode final int nightMode) {
28998fb75eaf45370ead657c78e03d5bf7b83f67a6bChris Banes        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
29098fb75eaf45370ead657c78e03d5bf7b83f67a6bChris Banes        instrumentation.runOnMainSync(new Runnable() {
29198fb75eaf45370ead657c78e03d5bf7b83f67a6bChris Banes            @Override
29298fb75eaf45370ead657c78e03d5bf7b83f67a6bChris Banes            public void run() {
29398fb75eaf45370ead657c78e03d5bf7b83f67a6bChris Banes                activity.getDelegate().setLocalNightMode(nightMode);
29498fb75eaf45370ead657c78e03d5bf7b83f67a6bChris Banes            }
29598fb75eaf45370ead657c78e03d5bf7b83f67a6bChris Banes        });
29698fb75eaf45370ead657c78e03d5bf7b83f67a6bChris Banes        instrumentation.waitForIdleSync();
29798fb75eaf45370ead657c78e03d5bf7b83f67a6bChris Banes    }
298244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikov}