1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.view.inputmethod.cts.util; 18 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertTrue; 21 22 import android.graphics.Bitmap; 23 import android.graphics.Color; 24 25 import androidx.annotation.ColorInt; 26 import androidx.annotation.NonNull; 27 28 import java.util.ArrayList; 29 30 /** 31 * A utility class to evaluate if {@link android.view.Window#setNavigationBarColor(int)} is 32 * supported on the device or not. 33 */ 34 public class NavigationBarColorVerifier { 35 36 private static final class ScreenShot { 37 @ColorInt 38 public final int mBackgroundColor; 39 @NonNull 40 public final int[] mPixels; 41 ScreenShot(@olorInt int backgroundColor, @NonNull Bitmap bitmap)42 ScreenShot(@ColorInt int backgroundColor, @NonNull Bitmap bitmap) { 43 mBackgroundColor = backgroundColor; 44 final int width = bitmap.getWidth(); 45 final int height = bitmap.getHeight(); 46 mPixels = new int[width * height]; 47 bitmap.getPixels(mPixels, 0 /* offset */, width /* stride */, 0 /* x */, 0 /* y */, 48 width, height); 49 } 50 } 51 52 public enum ResultType { 53 /** 54 * {@link android.view.Window#setNavigationBarColor(int)} seems to be not supported. 55 */ 56 NOT_SUPPORTED, 57 /** 58 * {@link android.view.Window#setNavigationBarColor(int)} seems to be supported. 59 */ 60 SUPPORTED, 61 } 62 63 static final class Result { 64 @NonNull 65 public final ResultType mResult; 66 @NonNull 67 public final String mAssertionMessage; 68 Result(@onNull ResultType result, @NonNull String assertionMessage)69 Result(@NonNull ResultType result, @NonNull String assertionMessage) { 70 mResult = result; 71 mAssertionMessage = assertionMessage; 72 } 73 74 @NonNull getResult()75 public ResultType getResult() { 76 return mResult; 77 } 78 79 @NonNull getAssertionMessage()80 public String getAssertionMessage() { 81 return mAssertionMessage; 82 } 83 } 84 85 @FunctionalInterface 86 public interface ScreenshotSupplier { 87 @NonNull takeScreenshot(@olorInt int navigationBarColor)88 Bitmap takeScreenshot(@ColorInt int navigationBarColor) throws Exception; 89 } 90 91 @NonNull verify(@onNull ScreenshotSupplier screenshotSupplier)92 static Result verify(@NonNull ScreenshotSupplier screenshotSupplier) throws Exception { 93 final ArrayList<ScreenShot> screenShots = new ArrayList<>(); 94 final int[] colors = new int[]{Color.RED, Color.GREEN, Color.BLUE}; 95 for (int color : colors) { 96 screenShots.add(new ScreenShot(color, screenshotSupplier.takeScreenshot(color))); 97 } 98 return verifyInternal(screenShots); 99 } 100 101 /** 102 * Asserts that {@link android.view.Window#setNavigationBarColor(int)} is supported on this 103 * device. 104 * 105 * @param screenshotSupplier callback to provide {@link Bitmap} of the navigation bar region 106 */ expectNavigationBarColorSupported( @onNull ScreenshotSupplier screenshotSupplier)107 public static void expectNavigationBarColorSupported( 108 @NonNull ScreenshotSupplier screenshotSupplier) throws Exception { 109 final Result result = verify(screenshotSupplier); 110 assertEquals(result.getAssertionMessage(), ResultType.SUPPORTED, result.getResult()); 111 } 112 113 /** 114 * Asserts that {@link android.view.Window#setNavigationBarColor(int)} is not supported on this 115 * device. 116 * 117 * @param screenshotSupplier callback to provide {@link Bitmap} of the navigation bar region 118 */ expectNavigationBarColorNotSupported( @onNull ScreenshotSupplier screenshotSupplier)119 public static void expectNavigationBarColorNotSupported( 120 @NonNull ScreenshotSupplier screenshotSupplier) throws Exception { 121 final Result result = verify(screenshotSupplier); 122 assertEquals(result.getAssertionMessage(), ResultType.NOT_SUPPORTED, result.getResult()); 123 } 124 verifyInternal(@onNull ArrayList<ScreenShot> screenShots)125 private static Result verifyInternal(@NonNull ArrayList<ScreenShot> screenShots) { 126 final int numScreenShots = screenShots.size(); 127 assertTrue( 128 "This algorithm requires at least 3 screen shots. size=" + numScreenShots, 129 numScreenShots >= 3); 130 assertEquals("All screenshots must have different background colors", 131 numScreenShots, 132 screenShots.stream() 133 .mapToInt(screenShot -> screenShot.mBackgroundColor) 134 .distinct() 135 .count()); 136 assertEquals("All screenshots must have the same pixel count", 137 1, 138 screenShots.stream() 139 .mapToInt(screenShot -> screenShot.mPixels.length) 140 .distinct() 141 .count()); 142 143 long numCompletelyFixedColorPixels = 0; 144 long numColorChangedAsRequestedPixels = 0; 145 final int numPixels = screenShots.get(0).mPixels.length; 146 for (int pixelIndex = 0; pixelIndex < numPixels; ++pixelIndex) { 147 final int i = pixelIndex; 148 final long numFoundColors = screenShots.stream() 149 .mapToInt(screenShot -> screenShot.mPixels[i]) 150 .distinct() 151 .count(); 152 if (numFoundColors == 1) { 153 numCompletelyFixedColorPixels++; 154 } 155 final long matchingScore = screenShots.stream() 156 .filter(screenShot -> screenShot.mPixels[i] == screenShot.mBackgroundColor) 157 .count(); 158 if (matchingScore == numScreenShots) { 159 numColorChangedAsRequestedPixels++; 160 } 161 } 162 final String assertionMessage = "numPixels=" + numPixels 163 + " numColorChangedAsRequestedPixels=" + numColorChangedAsRequestedPixels 164 + " numCompletelyFixedColorPixels=" + numCompletelyFixedColorPixels; 165 166 // OK, even 1 pixel is enough. 167 if (numColorChangedAsRequestedPixels > 0) { 168 return new Result(ResultType.SUPPORTED, assertionMessage); 169 } 170 171 return new Result(ResultType.NOT_SUPPORTED, assertionMessage); 172 } 173 } 174