1 /* 2 * Copyright (C) 2020 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 com.android.cts.mockime; 18 19 import android.graphics.Bitmap; 20 import android.graphics.Color; 21 22 import androidx.annotation.AnyThread; 23 import androidx.annotation.NonNull; 24 25 /** 26 * A utility class to put a unique image on {@link MockIme} so that whether the software keyboard is 27 * visible to the user or not can be determined from the screenshot. 28 */ 29 public final class Watermark { 30 /** 31 * Tolerance level between the expected color and the actual color in each color channel. 32 * 33 * <p>See Bug 174534092 about why we ended up having this.</p> 34 */ 35 private static final int TOLERANCE = 8; 36 37 /** 38 * A utility class that represents A8R8G8B bitmap as an integer array. 39 */ 40 private static final class BitmapImage { 41 @NonNull 42 private final int[] mPixels; 43 private final int mWidth; 44 private final int mHeight; 45 BitmapImage(@onNull int[] pixels, int width, int height)46 BitmapImage(@NonNull int[] pixels, int width, int height) { 47 mWidth = width; 48 mHeight = height; 49 mPixels = pixels; 50 } 51 52 /** 53 * Create {@link BitmapImage} from {@link Bitmap}. 54 * 55 * @param bitmap {@link Bitmap} from which {@link BitmapImage} will be created. 56 * @return A new instance of {@link BitmapImage}. 57 */ 58 @AnyThread createFromBitmap(@onNull Bitmap bitmap)59 static BitmapImage createFromBitmap(@NonNull Bitmap bitmap) { 60 final int width = bitmap.getWidth(); 61 final int height = bitmap.getHeight(); 62 final int[] pixels = new int[width * height]; 63 bitmap.getPixels(pixels, 0, width, 0, 0, width, height); 64 return new BitmapImage(pixels, width, height); 65 } 66 67 /** 68 * @return Width of this image. 69 */ 70 @AnyThread getWidth()71 int getWidth() { 72 return mWidth; 73 } 74 75 /** 76 * @return Height of this image. 77 */ 78 @AnyThread getHeight()79 int getHeight() { 80 return mHeight; 81 } 82 83 /** 84 * @return {@link Bitmap} that has the same pixel patterns. 85 */ 86 @AnyThread 87 @NonNull toBitmap()88 Bitmap toBitmap() { 89 return Bitmap.createBitmap(mPixels, mWidth, mHeight, Bitmap.Config.ARGB_8888); 90 } 91 92 /** 93 * Checks if the same image can be found in the specified {@link BitmapImage} within 94 * within {@link #TOLERANCE}. 95 * 96 * @param targetImage {@link BitmapImage} to be checked. 97 * @param offsetX X offset in the {@code targetImage} used when comparing. 98 * @param offsetY Y offset in the {@code targetImage} used when comparing. 99 * @return {@true} if two given images are the same within {@link #TOLERANCE}. 100 */ 101 @AnyThread robustMatch(@onNull BitmapImage targetImage, int offsetX, int offsetY)102 boolean robustMatch(@NonNull BitmapImage targetImage, int offsetX, int offsetY) { 103 final int targetWidth = targetImage.getWidth(); 104 final int targetHeight = targetImage.getHeight(); 105 final int[] targetPixels = targetImage.mPixels; 106 final int[] sourcePixels = mPixels; 107 108 if (offsetX < 0 || targetWidth <= mWidth - 1 + offsetX) return false; 109 if (offsetY < 0 || targetHeight <= mHeight - 1 + offsetY) return false; 110 111 for (int y = 0; y < mHeight; ++y) { 112 for (int x = 0; x < mWidth; ++x) { 113 final int targetX = x + offsetX; 114 final int targetY = y + offsetY; 115 final int targetPx = targetPixels[targetY * targetWidth + targetX]; 116 final int sourcePx = sourcePixels[y * mWidth + x]; 117 // Compares two given pixels (targetPx & sourcePx) to determine whether those 118 // two pixels are considered to be the same within {@link #TOLERANCE}. 119 boolean match = targetPx == sourcePx 120 || (Math.abs(Color.red(targetPx) - Color.red(sourcePx)) <= TOLERANCE 121 && Math.abs(Color.green(targetPx) - Color.green(sourcePx)) <= TOLERANCE 122 && Math.abs(Color.blue(targetPx) - Color.blue(sourcePx)) <= TOLERANCE); 123 if (!match) { 124 return false; 125 } 126 } 127 } 128 return true; 129 } 130 } 131 132 /** 133 * Not intended to be instantiated. 134 */ Watermark()135 private Watermark() { 136 } 137 138 private static final BitmapImage sImage; 139 static { 140 final long[] bitImage = new long[] { 141 0b0000000000000000000000000000000000000000000000000000000000000000L, 142 0b0000000000000000000000000000000000000000000000000000000000000000L, 143 0b0000000000000000000000000000000000000000000000000000000000000000L, 144 0b0001111111111111111111111111111111111111111111111111111111111000L, 145 0b0001111111111111111111111111111111111111111111111111111111111000L, 146 0b0001110010110100010000111100001000100001111000010001011010011000L, 147 0b0001100011111011001110101101100000001101101011100110111110011000L, 148 0b0001101101001011101111000011110111011110000111101110100101111000L, 149 0b0001111100000100110001010010011111110010010100011001000001111000L, 150 0b0001110010110100010000111100001000100001111000010001011010011000L, 151 0b0001110011111011001110101101100000001101101011100110111110011000L, 152 0b0001110101001011101111000011110111011110000111101110100101011000L, 153 0b0001100100000100110001010010011111110010010100011001000001011000L, 154 0b0001101110110100010000111100001000100001111000010001011011111000L, 155 0b0001111111111011001110101101100000001101101011100110111111111000L, 156 0b0001110011001011101111000011110111011110000111101110100110011000L, 157 0b0001100001000100110001010010011111110010010100011001000100011000L, 158 0b0001101101010100010000111100001000100001111000010001010101111000L, 159 0b0001111110011011001110101101100000001101101011100110110011111000L, 160 0b0001110010111011101111000011110111011110000111101110111010011000L, 161 0b0001100001111100110001010010011111110010010100011001111100011000L, 162 0b0001101101001000010000111100001000100001111000010000100101111000L, 163 0b0001111110000101001110101101100000001101101011100101000011111000L, 164 0b0001110010110100101111000011110111011110000111101001011010011000L, 165 0b0001100001111011110001010010011111110010010100011110111100011000L, 166 0b0001101101001011100000111100001000100001111000001110100101111000L, 167 0b0001111110000100010110101101100000001101101011010001000011111000L, 168 0b0001110010110100010011000011110111011110000110010001011010011000L, 169 0b0001100001111011101111010010011111110010010111101110111100011000L, 170 0b0001101101001011101111011100001000100001110111101110100101111000L, 171 0b0001111110000100010001011101100000001101110100010001000011111000L, 172 0b0001110010110100010000111101110111011101111000010001011010011000L, 173 0b0001100001111011101110100101100010001101001011101110111100011000L, 174 0b0001101101001011101111000011110111011110000111101110100101111000L, 175 0b0001111110000100010001011010011101110010110100010001000011111000L, 176 0b0001110010110100010000111100001000100001111000010001011010011000L, 177 0b0001100001111011101110100101100010001101001011101110111100011000L, 178 0b0001101101001011101111000011110111011110000111101110100101111000L, 179 0b0001111110000100010001011010011101110010110100010001000011111000L, 180 0b0001110010110100010000111100001000100001111000010001011010011000L, 181 0b0001100001111011101110100101100010001101001011101110111100011000L, 182 0b0001101101001011101111000011110111011110000111101110100101111000L, 183 0b0001111110000100010001011010011101110010110100010001000011111000L, 184 0b0001110010110100010000111100001000100001111000010001011010011000L, 185 0b0001100001111011101110100101100010001101001011101110111100011000L, 186 0b0001101101001011101111000011110111011110000111101110100101111000L, 187 0b0001111110000100010001011010011101110010110100010001000011111000L, 188 0b0001110010110100010000111100001000100001111000010001011010011000L, 189 0b0001100001111011101110100101100010001101001011101110111100011000L, 190 0b0001101101001011101111000011110111011110000111101110100101111000L, 191 0b0001111110000100010001011010011101110010110100010001000011111000L, 192 0b0001110010110100010000111100001000100001111000010001011010011000L, 193 0b0001100001111011101110100101100010001101001011101110111100011000L, 194 0b0001101101001011101111000011110111011110000111101110100101111000L, 195 0b0001111110000100010001011010011101110010110100010001000011111000L, 196 0b0001110010110100010000111100001000100001111000010001011010011000L, 197 0b0001100001111011101110100101100010001101001011101110111100011000L, 198 0b0001101101001011101111000011110111011110000111101110100101111000L, 199 0b0001111110000100010001011010011101110010110100010001000011111000L, 200 0b0001111111111111111111111111111111111111111111111111111111111000L, 201 0b0001111111111111111111111111111111111111111111111111111111111000L, 202 0b0000000000000000000000000000000000000000000000000000000000000000L, 203 0b0000000000000000000000000000000000000000000000000000000000000000L, 204 0b0000000000000000000000000000000000000000000000000000000000000000L, 205 }; 206 final int size = 64; 207 final long mask = 0b1000000000000000000000000000000000000000000000000000000000000000L; 208 final int[] pixels = new int[size * size]; 209 for (int y = 0; y < size; ++y) { 210 for (int x = 0; x < size; ++x) { 211 pixels[y * size + x] = 212 ((bitImage[y] << x) & mask) == mask ? 0xffffffff : 0xff000000; 213 } 214 } 215 sImage = new BitmapImage(pixels, size, size); 216 } 217 218 /** 219 * @return {@link Bitmap} object of the unique image. 220 */ create()221 public static Bitmap create() { 222 return sImage.toBitmap(); 223 } 224 225 /** 226 * Check the unique image can be found in the specified {@link Bitmap}. 227 * 228 * @param bitmap {@link Bitmap} to be checked. 229 * @return {@code true} if the corresponding unique image is found in the {@code bitmap}. 230 */ detect(@onNull Bitmap bitmap)231 public static boolean detect(@NonNull Bitmap bitmap) { 232 final BitmapImage targetImage = BitmapImage.createFromBitmap(bitmap); 233 234 // Search from the bottom line with an assumption that the IME is shown at the bottom. 235 final int targetImageHeight = targetImage.getHeight(); 236 final int targetImageWidth = targetImage.getWidth(); 237 for (int offsetY = targetImageHeight - 1; offsetY >= 0; --offsetY) { 238 for (int offsetX = 0; offsetX < targetImageWidth; ++offsetX) { 239 if (sImage.robustMatch(targetImage, offsetX, offsetY)) { 240 return true; 241 } 242 } 243 } 244 return false; 245 } 246 } 247