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}