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
19e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawaimport 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;
283ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikovimport android.support.v4.util.Pair;
294c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banesimport android.support.v7.widget.TintTypedArray;
30e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawaimport android.view.InputDevice;
31e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawaimport android.view.MotionEvent;
323ab41277a085b829e15eca69442cfe45cae58a8cKirill Grouchnikovimport android.view.View;
33e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawaimport android.view.ViewConfiguration;
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    /**
1761cd642752286e1fba44d8ec87e9793f116a22240Chris Banes     * Checks whether the center pixel in the specified drawable is of the same specified color.
1771cd642752286e1fba44d8ec87e9793f116a22240Chris Banes     *
1781cd642752286e1fba44d8ec87e9793f116a22240Chris Banes     * In case there is a color mismatch, the behavior of this method depends on the
1791cd642752286e1fba44d8ec87e9793f116a22240Chris Banes     * <code>throwExceptionIfFails</code> parameter. If it is <code>true</code>, this method will
1801cd642752286e1fba44d8ec87e9793f116a22240Chris Banes     * throw an <code>Exception</code> describing the mismatch. Otherwise this method will call
1811cd642752286e1fba44d8ec87e9793f116a22240Chris Banes     * <code>Assert.fail</code> with detailed description of the mismatch.
1821cd642752286e1fba44d8ec87e9793f116a22240Chris Banes     */
1831cd642752286e1fba44d8ec87e9793f116a22240Chris Banes    public static void assertCenterPixelOfColor(String failMessagePrefix, @NonNull Drawable drawable,
1841cd642752286e1fba44d8ec87e9793f116a22240Chris Banes            int drawableWidth, int drawableHeight, boolean callSetBounds, @ColorInt int color,
1851cd642752286e1fba44d8ec87e9793f116a22240Chris Banes            int allowedComponentVariance, boolean throwExceptionIfFails) {
1861cd642752286e1fba44d8ec87e9793f116a22240Chris Banes        // Create a bitmap
1871cd642752286e1fba44d8ec87e9793f116a22240Chris Banes        Bitmap bitmap = Bitmap.createBitmap(drawableWidth, drawableHeight, Bitmap.Config.ARGB_8888);
1881cd642752286e1fba44d8ec87e9793f116a22240Chris Banes        // Create a canvas that wraps the bitmap
1891cd642752286e1fba44d8ec87e9793f116a22240Chris Banes        Canvas canvas = new Canvas(bitmap);
1901cd642752286e1fba44d8ec87e9793f116a22240Chris Banes        if (callSetBounds) {
1911cd642752286e1fba44d8ec87e9793f116a22240Chris Banes            // Configure the drawable to have bounds that match the passed size
1921cd642752286e1fba44d8ec87e9793f116a22240Chris Banes            drawable.setBounds(0, 0, drawableWidth, drawableHeight);
1931cd642752286e1fba44d8ec87e9793f116a22240Chris Banes        }
1941cd642752286e1fba44d8ec87e9793f116a22240Chris Banes        // And ask the drawable to draw itself to the canvas / bitmap
1951cd642752286e1fba44d8ec87e9793f116a22240Chris Banes        drawable.draw(canvas);
1961cd642752286e1fba44d8ec87e9793f116a22240Chris Banes
1971cd642752286e1fba44d8ec87e9793f116a22240Chris Banes        try {
1981cd642752286e1fba44d8ec87e9793f116a22240Chris Banes            assertCenterPixelOfColor(failMessagePrefix, bitmap, color, allowedComponentVariance,
1991cd642752286e1fba44d8ec87e9793f116a22240Chris Banes                    throwExceptionIfFails);
2001cd642752286e1fba44d8ec87e9793f116a22240Chris Banes        } finally {
2011cd642752286e1fba44d8ec87e9793f116a22240Chris Banes            bitmap.recycle();
2021cd642752286e1fba44d8ec87e9793f116a22240Chris Banes        }
2031cd642752286e1fba44d8ec87e9793f116a22240Chris Banes    }
2041cd642752286e1fba44d8ec87e9793f116a22240Chris Banes
2051cd642752286e1fba44d8ec87e9793f116a22240Chris 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,
2141cd642752286e1fba44d8ec87e9793f116a22240Chris 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    }
286e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa
287e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa    /**
288e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa     * Emulates a tap on a point relative to the top-left corner of the passed {@link View}. Offset
289e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa     * parameters are used to compute the final screen coordinates of the tap point.
290e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa     *
291e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa     * @param instrumentation the instrumentation used to run the test
292e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa     * @param anchorView the anchor view to determine the tap location on the screen
293e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa     * @param offsetX extra X offset for the tap
294e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa     * @param offsetY extra Y offset for the tap
295e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa     */
296e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa    public static void emulateTapOnView(Instrumentation instrumentation, View anchorView,
297e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa            int offsetX, int offsetY) {
298e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa        final int touchSlop = ViewConfiguration.get(anchorView.getContext()).getScaledTouchSlop();
299e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa        // Get anchor coordinates on the screen
300e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa        final int[] viewOnScreenXY = new int[2];
301e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa        anchorView.getLocationOnScreen(viewOnScreenXY);
302e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa        int xOnScreen = viewOnScreenXY[0] + offsetX;
303e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa        int yOnScreen = viewOnScreenXY[1] + offsetY;
304e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa        final long downTime = SystemClock.uptimeMillis();
305e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa
306e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa        injectDownEvent(instrumentation, downTime, xOnScreen, yOnScreen);
307e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa        injectMoveEventForTap(instrumentation, downTime, touchSlop, xOnScreen, yOnScreen);
308e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa        injectUpEvent(instrumentation, downTime, false, xOnScreen, yOnScreen);
309e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa
310e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa        // Wait for the system to process all events in the queue
311e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa        instrumentation.waitForIdleSync();
312e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa    }
313e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa
314e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa    private static long injectDownEvent(Instrumentation instrumentation, long downTime,
315e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa            int xOnScreen, int yOnScreen) {
316e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa        MotionEvent eventDown = MotionEvent.obtain(
317e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa                downTime, downTime, MotionEvent.ACTION_DOWN, xOnScreen, yOnScreen, 1);
318e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa        eventDown.setSource(InputDevice.SOURCE_TOUCHSCREEN);
319e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa        instrumentation.sendPointerSync(eventDown);
320e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa        eventDown.recycle();
321e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa        return downTime;
322e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa    }
323e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa
324e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa    private static void injectMoveEventForTap(Instrumentation instrumentation, long downTime,
325e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa            int touchSlop, int xOnScreen, int yOnScreen) {
326e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa        MotionEvent eventMove = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_MOVE,
327e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa                xOnScreen + (touchSlop / 2.0f), yOnScreen + (touchSlop / 2.0f), 1);
328e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa        eventMove.setSource(InputDevice.SOURCE_TOUCHSCREEN);
329e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa        instrumentation.sendPointerSync(eventMove);
330e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa        eventMove.recycle();
331e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa    }
332e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa
333e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa
334e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa    private static void injectUpEvent(Instrumentation instrumentation, long downTime,
335e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa            boolean useCurrentEventTime, int xOnScreen, int yOnScreen) {
336e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa        long eventTime = useCurrentEventTime ? SystemClock.uptimeMillis() : downTime;
337e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa        MotionEvent eventUp = MotionEvent.obtain(
338e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa                downTime, eventTime, MotionEvent.ACTION_UP, xOnScreen, yOnScreen, 1);
339e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa        eventUp.setSource(InputDevice.SOURCE_TOUCHSCREEN);
340e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa        instrumentation.sendPointerSync(eventUp);
341e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa        eventUp.recycle();
342e5b8e35502caacd6061eb3320e987f96aa39450bYohei Yukawa    }
343244abf1fee3fe4fab72a1d8925407e29219940beKirill Grouchnikov}