1 /* 2 * Copyright (C) 2006 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.graphics.drawable; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import android.graphics.Bitmap; 22 import android.graphics.Canvas; 23 import android.graphics.Region; 24 import android.os.Handler; 25 import android.os.HandlerThread; 26 import android.os.Parcel; 27 import android.test.AndroidTestCase; 28 import android.util.Log; 29 30 import androidx.test.filters.SmallTest; 31 32 import com.android.frameworks.coretests.R; 33 34 import java.io.ByteArrayOutputStream; 35 import java.io.File; 36 import java.io.FileOutputStream; 37 import java.util.ArrayList; 38 import java.util.Arrays; 39 40 public class IconTest extends AndroidTestCase { 41 public static final String TAG = IconTest.class.getSimpleName(); L(String s, Object... parts)42 public static void L(String s, Object... parts) { 43 Log.d(TAG, (parts.length == 0) ? s : String.format(s, parts)); 44 } 45 46 @SmallTest testWithBitmap()47 public void testWithBitmap() throws Exception { 48 final Bitmap bm1 = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888); 49 final Bitmap bm2 = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565); 50 final Bitmap bm3 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape)) 51 .getBitmap(); 52 53 final Canvas can1 = new Canvas(bm1); 54 can1.drawColor(0xFFFF0000); 55 final Canvas can2 = new Canvas(bm2); 56 can2.drawColor(0xFF00FF00); 57 58 final Icon im1 = Icon.createWithBitmap(bm1); 59 final Icon im2 = Icon.createWithBitmap(bm2); 60 final Icon im3 = Icon.createWithBitmap(bm3); 61 62 final Drawable draw1 = im1.loadDrawable(mContext); 63 final Drawable draw2 = im2.loadDrawable(mContext); 64 final Drawable draw3 = im3.loadDrawable(mContext); 65 66 final Bitmap test1 = Bitmap.createBitmap(draw1.getIntrinsicWidth(), 67 draw1.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); 68 final Bitmap test2 = Bitmap.createBitmap(draw2.getIntrinsicWidth(), 69 draw2.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); 70 final Bitmap test3 = Bitmap.createBitmap(draw3.getIntrinsicWidth(), 71 draw3.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); 72 73 draw1.setBounds(0, 0, draw1.getIntrinsicWidth(), draw1.getIntrinsicHeight()); 74 draw1.draw(new Canvas(test1)); 75 76 draw2.setBounds(0, 0, draw2.getIntrinsicWidth(), draw2.getIntrinsicHeight()); 77 draw2.draw(new Canvas(test2)); 78 79 draw3.setBounds(0, 0, draw3.getIntrinsicWidth(), draw3.getIntrinsicHeight()); 80 draw3.draw(new Canvas(test3)); 81 82 final File dir = getContext().getExternalFilesDir(null); 83 L("writing temp bitmaps to %s...", dir); 84 85 bm1.compress(Bitmap.CompressFormat.PNG, 100, 86 new FileOutputStream(new File(dir, "bitmap1-original.png"))); 87 test1.compress(Bitmap.CompressFormat.PNG, 100, 88 new FileOutputStream(new File(dir, "bitmap1-test.png"))); 89 if (!equalBitmaps(bm1, test1)) { 90 findBitmapDifferences(bm1, test1); 91 fail("bitmap1 differs, check " + dir); 92 } 93 94 bm2.compress(Bitmap.CompressFormat.PNG, 100, 95 new FileOutputStream(new File(dir, "bitmap2-original.png"))); 96 test2.compress(Bitmap.CompressFormat.PNG, 100, 97 new FileOutputStream(new File(dir, "bitmap2-test.png"))); 98 if (!equalBitmaps(bm2, test2)) { 99 findBitmapDifferences(bm2, test2); 100 fail("bitmap2 differs, check " + dir); 101 } 102 103 bm3.compress(Bitmap.CompressFormat.PNG, 100, 104 new FileOutputStream(new File(dir, "bitmap3-original.png"))); 105 test3.compress(Bitmap.CompressFormat.PNG, 100, 106 new FileOutputStream(new File(dir, "bitmap3-test.png"))); 107 if (!equalBitmaps(bm3, test3)) { 108 findBitmapDifferences(bm3, test3); 109 fail("bitmap3 differs, check " + dir); 110 } 111 } 112 113 @SmallTest testScaleDownIfNecessary()114 public void testScaleDownIfNecessary() throws Exception { 115 final Bitmap bm = Bitmap.createBitmap(4321, 78, Bitmap.Config.ARGB_8888); 116 final Icon ic = Icon.createWithBitmap(bm); 117 ic.scaleDownIfNecessary(40, 20); 118 119 assertThat(bm.getWidth()).isEqualTo(4321); 120 assertThat(bm.getHeight()).isEqualTo(78); 121 122 assertThat(ic.getBitmap().getWidth()).isLessThan(41); 123 assertThat(ic.getBitmap().getHeight()).isLessThan(21); 124 } 125 126 @SmallTest testWithAdaptiveBitmap()127 public void testWithAdaptiveBitmap() throws Exception { 128 final Bitmap bm1 = Bitmap.createBitmap(150, 150, Bitmap.Config.ARGB_8888); 129 130 final Canvas can1 = new Canvas(bm1); 131 can1.drawColor(0xFFFF0000); 132 133 final Icon im1 = Icon.createWithAdaptiveBitmap(bm1); 134 135 final AdaptiveIconDrawable draw1 = (AdaptiveIconDrawable) im1.loadDrawable(mContext); 136 137 final Bitmap test1 = Bitmap.createBitmap( 138 (int)(draw1.getIntrinsicWidth() * (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction())), 139 (int)(draw1.getIntrinsicHeight() * (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction())), 140 Bitmap.Config.ARGB_8888); 141 142 draw1.setBounds(0, 0, 143 (int) (draw1.getIntrinsicWidth() * (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction())), 144 (int) (draw1.getIntrinsicHeight() * (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction()))); 145 draw1.draw(new Canvas(test1)); 146 147 final File dir = getContext().getExternalFilesDir(null); 148 L("writing temp bitmaps to %s...", dir); 149 150 bm1.compress(Bitmap.CompressFormat.PNG, 100, 151 new FileOutputStream(new File(dir, "adaptive-bitmap1-original.png"))); 152 test1.compress(Bitmap.CompressFormat.PNG, 100, 153 new FileOutputStream(new File(dir, "adaptive-bitmap1-test.png"))); 154 if (!equalBitmaps(bm1, test1, draw1.getSafeZone())) { 155 findBitmapDifferences(bm1, test1); 156 fail("adaptive bitmap1 differs, check " + dir); 157 } 158 } 159 160 @SmallTest testWithBitmapResource()161 public void testWithBitmapResource() throws Exception { 162 final Bitmap res1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape)) 163 .getBitmap(); 164 165 final Icon im1 = Icon.createWithResource(getContext(), R.drawable.landscape); 166 final Drawable draw1 = im1.loadDrawable(mContext); 167 final Bitmap test1 = Bitmap.createBitmap(draw1.getIntrinsicWidth(), 168 draw1.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); 169 draw1.setBounds(0, 0, test1.getWidth(), test1.getHeight()); 170 draw1.draw(new Canvas(test1)); 171 172 final File dir = getContext().getExternalFilesDir(null); 173 res1.compress(Bitmap.CompressFormat.PNG, 100, 174 new FileOutputStream(new File(dir, "res1-original.png"))); 175 test1.compress(Bitmap.CompressFormat.PNG, 100, 176 new FileOutputStream(new File(dir, "res1-test.png"))); 177 if (!equalBitmaps(res1, test1)) { 178 findBitmapDifferences(res1, test1); 179 fail("res1 differs, check " + dir); 180 } 181 } 182 183 /** 184 * Icon resource test that ensures we can load and draw non-bitmaps. (In this case, 185 * stat_sys_adb is assumed, and asserted, to be a vector drawable.) 186 */ 187 @SmallTest testWithStatSysAdbResource()188 public void testWithStatSysAdbResource() throws Exception { 189 // establish reference bitmap 190 final float dp = getContext().getResources().getDisplayMetrics().density; 191 final int stat_sys_adb_width = (int) (24 * dp); 192 final int stat_sys_adb_height = (int) (24 * dp); 193 194 final Drawable stat_sys_adb = getContext() 195 .getDrawable(com.android.internal.R.drawable.stat_sys_adb); 196 if (!(stat_sys_adb instanceof VectorDrawable)) { 197 fail("stat_sys_adb is a " + stat_sys_adb.toString() 198 + ", not a VectorDrawable; stat_sys_adb malformed"); 199 } 200 201 if (stat_sys_adb.getIntrinsicWidth() != stat_sys_adb_width) { 202 fail("intrinsic width of stat_sys_adb is not 24dp; stat_sys_adb malformed"); 203 } 204 if (stat_sys_adb.getIntrinsicHeight() != stat_sys_adb_height) { 205 fail("intrinsic height of stat_sys_adb is not 24dp; stat_sys_adb malformed"); 206 } 207 final Bitmap referenceBitmap = Bitmap.createBitmap( 208 stat_sys_adb_width, 209 stat_sys_adb_height, 210 Bitmap.Config.ARGB_8888); 211 stat_sys_adb.setBounds(0, 0, stat_sys_adb_width, stat_sys_adb_height); 212 stat_sys_adb.draw(new Canvas(referenceBitmap)); 213 214 final Icon im1 = Icon.createWithResource(getContext(), 215 com.android.internal.R.drawable.stat_sys_adb); 216 final Drawable draw1 = im1.loadDrawable(getContext()); 217 218 assertEquals(stat_sys_adb.getIntrinsicWidth(), draw1.getIntrinsicWidth()); 219 assertEquals(stat_sys_adb.getIntrinsicHeight(), draw1.getIntrinsicHeight()); 220 assertEquals(im1.getResId(), com.android.internal.R.drawable.stat_sys_adb); 221 222 final Bitmap test1 = Bitmap.createBitmap( 223 draw1.getIntrinsicWidth(), 224 draw1.getIntrinsicHeight(), 225 Bitmap.Config.ARGB_8888); 226 draw1.setBounds(0, 0, test1.getWidth(), test1.getHeight()); 227 draw1.draw(new Canvas(test1)); 228 229 final File dir = getContext().getExternalFilesDir(null); 230 test1.compress(Bitmap.CompressFormat.PNG, 100, 231 new FileOutputStream(new File(dir, "testWithVectorDrawableResource-test.png"))); 232 if (!equalBitmaps(referenceBitmap, test1)) { 233 findBitmapDifferences(referenceBitmap, test1); 234 fail("testWithFile: file1 differs, check " + dir); 235 } 236 } 237 238 @SmallTest testWithFile()239 public void testWithFile() throws Exception { 240 final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape)) 241 .getBitmap(); 242 final File dir = getContext().getExternalFilesDir(null); 243 final File file1 = new File(dir, "file1-original.png"); 244 bit1.compress(Bitmap.CompressFormat.PNG, 100, 245 new FileOutputStream(file1)); 246 247 final Icon im1 = Icon.createWithFilePath(file1.toString()); 248 final Drawable draw1 = im1.loadDrawable(mContext); 249 final Bitmap test1 = Bitmap.createBitmap(draw1.getIntrinsicWidth(), 250 draw1.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); 251 draw1.setBounds(0, 0, test1.getWidth(), test1.getHeight()); 252 draw1.draw(new Canvas(test1)); 253 254 test1.compress(Bitmap.CompressFormat.PNG, 100, 255 new FileOutputStream(new File(dir, "file1-test.png"))); 256 if (!equalBitmaps(bit1, test1)) { 257 findBitmapDifferences(bit1, test1); 258 fail("testWithFile: file1 differs, check " + dir); 259 } 260 } 261 262 @SmallTest testAsync()263 public void testAsync() throws Exception { 264 final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape)) 265 .getBitmap(); 266 final File dir = getContext().getExternalFilesDir(null); 267 final File file1 = new File(dir, "async-original.png"); 268 bit1.compress(Bitmap.CompressFormat.PNG, 100, 269 new FileOutputStream(file1)); 270 271 final Icon im1 = Icon.createWithFilePath(file1.toString()); 272 final HandlerThread thd = new HandlerThread("testAsync"); 273 thd.start(); 274 final Handler h = new Handler(thd.getLooper()); 275 L(TAG, "asyncTest: dispatching load to thread: " + thd); 276 im1.loadDrawableAsync(mContext, new Icon.OnDrawableLoadedListener() { 277 @Override 278 public void onDrawableLoaded(Drawable draw1) { 279 L(TAG, "asyncTest: thread: loading drawable"); 280 L(TAG, "asyncTest: thread: loaded: %dx%d", draw1.getIntrinsicWidth(), 281 draw1.getIntrinsicHeight()); 282 final Bitmap test1 = Bitmap.createBitmap(draw1.getIntrinsicWidth(), 283 draw1.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); 284 draw1.setBounds(0, 0, test1.getWidth(), test1.getHeight()); 285 draw1.draw(new Canvas(test1)); 286 287 try { 288 test1.compress(Bitmap.CompressFormat.PNG, 100, 289 new FileOutputStream(new File(dir, "async-test.png"))); 290 } catch (java.io.FileNotFoundException ex) { 291 fail("couldn't create test file: " + ex); 292 } 293 if (!equalBitmaps(bit1, test1)) { 294 findBitmapDifferences(bit1, test1); 295 fail("testAsync: file1 differs, check " + dir); 296 } 297 } 298 }, h); 299 L(TAG, "asyncTest: awaiting result"); 300 Thread.sleep(500); // ;_; 301 assertTrue("async-test.png does not exist!", new File(dir, "async-test.png").exists()); 302 L(TAG, "asyncTest: done"); 303 } 304 305 @SmallTest testParcel()306 public void testParcel() throws Exception { 307 final Bitmap originalbits = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape)) 308 .getBitmap(); 309 310 final ByteArrayOutputStream ostream = new ByteArrayOutputStream( 311 originalbits.getWidth() * originalbits.getHeight() * 2); // guess 50% compression 312 originalbits.compress(Bitmap.CompressFormat.PNG, 100, ostream); 313 final byte[] pngdata = ostream.toByteArray(); 314 315 L("starting testParcel; bitmap: %d bytes, PNG: %d bytes", 316 originalbits.getByteCount(), 317 pngdata.length); 318 319 final File dir = getContext().getExternalFilesDir(null); 320 final File originalfile = new File(dir, "parcel-original.png"); 321 new FileOutputStream(originalfile).write(pngdata); 322 323 ArrayList<Icon> imgs = new ArrayList<>(); 324 final Icon file1 = Icon.createWithFilePath(originalfile.getAbsolutePath()); 325 imgs.add(file1); 326 final Icon bit1 = Icon.createWithBitmap(originalbits); 327 imgs.add(bit1); 328 final Icon data1 = Icon.createWithData(pngdata, 0, pngdata.length); 329 imgs.add(data1); 330 final Icon res1 = Icon.createWithResource(getContext(), R.drawable.landscape); 331 imgs.add(res1); 332 333 ArrayList<Icon> test = new ArrayList<>(); 334 final Parcel parcel = Parcel.obtain(); 335 int pos = 0; 336 parcel.writeInt(imgs.size()); 337 for (Icon img : imgs) { 338 img.writeToParcel(parcel, 0); 339 L("used %d bytes parceling: %s", parcel.dataPosition() - pos, img); 340 pos = parcel.dataPosition(); 341 } 342 343 parcel.setDataPosition(0); // rewind 344 final int N = parcel.readInt(); 345 for (int i=0; i<N; i++) { 346 Icon img = Icon.CREATOR.createFromParcel(parcel); 347 L("test %d: read from parcel: %s", i, img); 348 final File testfile = new File(dir, 349 String.format("parcel-test%02d.png", i)); 350 351 final Drawable draw1 = img.loadDrawable(mContext); 352 if (draw1 == null) { 353 fail("null drawable from img: " + img); 354 } 355 final Bitmap test1 = Bitmap.createBitmap(draw1.getIntrinsicWidth(), 356 draw1.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); 357 draw1.setBounds(0, 0, test1.getWidth(), test1.getHeight()); 358 draw1.draw(new Canvas(test1)); 359 360 try { 361 test1.compress(Bitmap.CompressFormat.PNG, 100, 362 new FileOutputStream(testfile)); 363 } catch (java.io.FileNotFoundException ex) { 364 fail("couldn't create test file " + testfile + ": " + ex); 365 } 366 if (!equalBitmaps(originalbits, test1)) { 367 findBitmapDifferences(originalbits, test1); 368 fail(testfile + " differs from original: " + originalfile); 369 } 370 371 } 372 } 373 374 375 // ======== utils ======== 376 377 static final char[] GRADIENT = " .:;+=xX$#".toCharArray(); 378 static float[] hsv = new float[3]; colorToChar(int color)379 static char colorToChar(int color) { 380 int sum = ((color >> 16) & 0xff) 381 + ((color >> 8) & 0xff) 382 + ((color) & 0xff); 383 return GRADIENT[sum * (GRADIENT.length-1) / (3*0xff)]; 384 } printBits(int[] a, int w, int h)385 static void printBits(int[] a, int w, int h) { 386 final StringBuilder sb = new StringBuilder(); 387 for (int i=0; i<w; i++) { 388 for (int j=0; j<h; j++) { 389 sb.append(colorToChar(a[i+w*j])); 390 } 391 sb.append('\n'); 392 } 393 L(sb.toString()); 394 } printBits(Bitmap a)395 static void printBits(Bitmap a) { 396 final int w = a.getWidth(); 397 final int h = a.getHeight(); 398 int[] aPix = new int[w * h]; 399 printBits(aPix, w, h); 400 } equalBitmaps(Bitmap a, Bitmap b)401 boolean equalBitmaps(Bitmap a, Bitmap b) { 402 return equalBitmaps(a, b, null); 403 } 404 equalBitmaps(Bitmap a, Bitmap b, Region region)405 boolean equalBitmaps(Bitmap a, Bitmap b, Region region) { 406 if (a.getWidth() != b.getWidth() || a.getHeight() != b.getHeight()) return false; 407 408 final int w = a.getWidth(); 409 final int h = a.getHeight(); 410 int[] aPix = new int[w * h]; 411 int[] bPix = new int[w * h]; 412 413 if (region != null) { 414 for (int i = 0; i < w; i++) { 415 for (int j = 0; j < h; j++) { 416 if (region.contains(i, j) && a.getPixel(i, j) != b.getPixel(i, j)) { 417 return false; 418 } 419 } 420 } 421 return true; 422 } else { 423 a.getPixels(aPix, 0, w, 0, 0, w, h); 424 b.getPixels(bPix, 0, w, 0, 0, w, h); 425 return Arrays.equals(aPix, bPix); 426 } 427 } 428 findBitmapDifferences(Bitmap a, Bitmap b)429 void findBitmapDifferences(Bitmap a, Bitmap b) { 430 if (a.getWidth() != b.getWidth() || a.getHeight() != b.getHeight()) { 431 L("different sizes: %dx%d vs %dx%d", 432 a.getWidth(), a.getHeight(), b.getWidth(), b.getHeight()); 433 return; 434 } 435 436 final int w = a.getWidth(); 437 final int h = a.getHeight(); 438 int[] aPix = new int[w * h]; 439 int[] bPix = new int[w * h]; 440 441 a.getPixels(aPix, 0, w, 0, 0, w, h); 442 b.getPixels(bPix, 0, w, 0, 0, w, h); 443 444 L("bitmap a (%dx%d)", w, h); 445 printBits(aPix, w, h); 446 L("bitmap b (%dx%d)", w, h); 447 printBits(bPix, w, h); 448 449 StringBuffer sb = new StringBuffer("Different pixels: "); 450 for (int i=0; i<w; i++) { 451 for (int j=0; j<h; j++) { 452 if (aPix[i+w*j] != bPix[i+w*j]) { 453 sb.append(" ").append(i).append(",").append(j); 454 } 455 } 456 } 457 L(sb.toString()); 458 } 459 } 460