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}