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 static org.junit.Assert.assertEquals; 22 import static org.junit.Assert.assertTrue; 23 import static org.junit.Assert.fail; 24 25 import android.app.IUriGrantsManager; 26 import android.content.ContentProvider; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.pm.ParceledListSlice; 30 import android.graphics.Bitmap; 31 import android.graphics.Canvas; 32 import android.graphics.RecordingCanvas; 33 import android.graphics.Region; 34 import android.net.Uri; 35 import android.os.Handler; 36 import android.os.HandlerThread; 37 import android.os.IBinder; 38 import android.os.Parcel; 39 import android.os.RemoteException; 40 import android.util.Log; 41 42 import androidx.test.core.app.ApplicationProvider; 43 44 import com.android.frameworks.coretests.R; 45 46 import com.google.testing.junit.testparameterinjector.TestParameter; 47 import com.google.testing.junit.testparameterinjector.TestParameterInjector; 48 49 import java.io.ByteArrayOutputStream; 50 import java.io.File; 51 import java.io.FileNotFoundException; 52 import java.io.FileOutputStream; 53 import java.util.ArrayList; 54 import java.util.Arrays; 55 56 import org.junit.Before; 57 import org.junit.Test; 58 import org.junit.runner.RunWith; 59 60 @RunWith(TestParameterInjector.class) 61 public class IconTest { 62 public static final String TAG = IconTest.class.getSimpleName(); 63 private Context mContext; 64 L(String s, Object... parts)65 public static void L(String s, Object... parts) { 66 Log.d(TAG, (parts.length == 0) ? s : String.format(s, parts)); 67 } 68 getContext()69 private Context getContext() { 70 return mContext; 71 } 72 73 @Before setup()74 public void setup() { 75 mContext = ApplicationProvider.getApplicationContext(); 76 } 77 78 @Test testWithBitmap()79 public void testWithBitmap() throws Exception { 80 final Bitmap bm1 = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888); 81 final Bitmap bm2 = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565); 82 final Bitmap bm3 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape)) 83 .getBitmap(); 84 85 final Canvas can1 = new Canvas(bm1); 86 can1.drawColor(0xFFFF0000); 87 final Canvas can2 = new Canvas(bm2); 88 can2.drawColor(0xFF00FF00); 89 90 final Icon im1 = Icon.createWithBitmap(bm1); 91 final Icon im2 = Icon.createWithBitmap(bm2); 92 final Icon im3 = Icon.createWithBitmap(bm3); 93 94 final Drawable draw1 = im1.loadDrawable(mContext); 95 final Drawable draw2 = im2.loadDrawable(mContext); 96 final Drawable draw3 = im3.loadDrawable(mContext); 97 98 final Bitmap test1 = Bitmap.createBitmap(draw1.getIntrinsicWidth(), 99 draw1.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); 100 final Bitmap test2 = Bitmap.createBitmap(draw2.getIntrinsicWidth(), 101 draw2.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); 102 final Bitmap test3 = Bitmap.createBitmap(draw3.getIntrinsicWidth(), 103 draw3.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); 104 105 draw1.setBounds(0, 0, draw1.getIntrinsicWidth(), draw1.getIntrinsicHeight()); 106 draw1.draw(new Canvas(test1)); 107 108 draw2.setBounds(0, 0, draw2.getIntrinsicWidth(), draw2.getIntrinsicHeight()); 109 draw2.draw(new Canvas(test2)); 110 111 draw3.setBounds(0, 0, draw3.getIntrinsicWidth(), draw3.getIntrinsicHeight()); 112 draw3.draw(new Canvas(test3)); 113 114 final File dir = getContext().getExternalFilesDir(null); 115 L("writing temp bitmaps to %s...", dir); 116 117 bm1.compress(Bitmap.CompressFormat.PNG, 100, 118 new FileOutputStream(new File(dir, "bitmap1-original.png"))); 119 test1.compress(Bitmap.CompressFormat.PNG, 100, 120 new FileOutputStream(new File(dir, "bitmap1-test.png"))); 121 if (!equalBitmaps(bm1, test1)) { 122 findBitmapDifferences(bm1, test1); 123 fail("bitmap1 differs, check " + dir); 124 } 125 126 bm2.compress(Bitmap.CompressFormat.PNG, 100, 127 new FileOutputStream(new File(dir, "bitmap2-original.png"))); 128 test2.compress(Bitmap.CompressFormat.PNG, 100, 129 new FileOutputStream(new File(dir, "bitmap2-test.png"))); 130 if (!equalBitmaps(bm2, test2)) { 131 findBitmapDifferences(bm2, test2); 132 fail("bitmap2 differs, check " + dir); 133 } 134 135 bm3.compress(Bitmap.CompressFormat.PNG, 100, 136 new FileOutputStream(new File(dir, "bitmap3-original.png"))); 137 test3.compress(Bitmap.CompressFormat.PNG, 100, 138 new FileOutputStream(new File(dir, "bitmap3-test.png"))); 139 if (!equalBitmaps(bm3, test3)) { 140 findBitmapDifferences(bm3, test3); 141 fail("bitmap3 differs, check " + dir); 142 } 143 } 144 145 @Test testScaleDownIfNecessary()146 public void testScaleDownIfNecessary() throws Exception { 147 final Bitmap bm = Bitmap.createBitmap(4321, 78, Bitmap.Config.ARGB_8888); 148 final Icon ic = Icon.createWithBitmap(bm); 149 ic.scaleDownIfNecessary(40, 20); 150 151 assertThat(bm.getWidth()).isEqualTo(4321); 152 assertThat(bm.getHeight()).isEqualTo(78); 153 154 assertThat(ic.getBitmap().getWidth()).isLessThan(41); 155 assertThat(ic.getBitmap().getHeight()).isLessThan(21); 156 } 157 158 @Test testWithAdaptiveBitmap()159 public void testWithAdaptiveBitmap() throws Exception { 160 final Bitmap bm1 = Bitmap.createBitmap(150, 150, Bitmap.Config.ARGB_8888); 161 162 final Canvas can1 = new Canvas(bm1); 163 can1.drawColor(0xFFFF0000); 164 165 final Icon im1 = Icon.createWithAdaptiveBitmap(bm1); 166 167 final AdaptiveIconDrawable draw1 = (AdaptiveIconDrawable) im1.loadDrawable(mContext); 168 169 final Bitmap test1 = Bitmap.createBitmap( 170 (int)(draw1.getIntrinsicWidth() * (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction())), 171 (int)(draw1.getIntrinsicHeight() * (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction())), 172 Bitmap.Config.ARGB_8888); 173 174 draw1.setBounds(0, 0, 175 (int) (draw1.getIntrinsicWidth() * (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction())), 176 (int) (draw1.getIntrinsicHeight() * (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction()))); 177 draw1.draw(new Canvas(test1)); 178 179 final File dir = getContext().getExternalFilesDir(null); 180 L("writing temp bitmaps to %s...", dir); 181 182 bm1.compress(Bitmap.CompressFormat.PNG, 100, 183 new FileOutputStream(new File(dir, "adaptive-bitmap1-original.png"))); 184 test1.compress(Bitmap.CompressFormat.PNG, 100, 185 new FileOutputStream(new File(dir, "adaptive-bitmap1-test.png"))); 186 if (!equalBitmaps(bm1, test1, draw1.getSafeZone())) { 187 findBitmapDifferences(bm1, test1); 188 fail("adaptive bitmap1 differs, check " + dir); 189 } 190 } 191 192 @Test testWithBitmapResource()193 public void testWithBitmapResource() throws Exception { 194 final Bitmap res1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape)) 195 .getBitmap(); 196 197 final Icon im1 = Icon.createWithResource(getContext(), R.drawable.landscape); 198 final Drawable draw1 = im1.loadDrawable(mContext); 199 final Bitmap test1 = Bitmap.createBitmap(draw1.getIntrinsicWidth(), 200 draw1.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); 201 draw1.setBounds(0, 0, test1.getWidth(), test1.getHeight()); 202 draw1.draw(new Canvas(test1)); 203 204 final File dir = getContext().getExternalFilesDir(null); 205 res1.compress(Bitmap.CompressFormat.PNG, 100, 206 new FileOutputStream(new File(dir, "res1-original.png"))); 207 test1.compress(Bitmap.CompressFormat.PNG, 100, 208 new FileOutputStream(new File(dir, "res1-test.png"))); 209 if (!equalBitmaps(res1, test1)) { 210 findBitmapDifferences(res1, test1); 211 fail("res1 differs, check " + dir); 212 } 213 } 214 215 /** 216 * Icon resource test that ensures we can load and draw non-bitmaps. (In this case, 217 * stat_sys_adb is assumed, and asserted, to be a vector drawable.) 218 */ 219 @Test testWithStatSysAdbResource()220 public void testWithStatSysAdbResource() throws Exception { 221 // establish reference bitmap 222 final float dp = getContext().getResources().getDisplayMetrics().density; 223 final int stat_sys_adb_width = (int) (24 * dp); 224 final int stat_sys_adb_height = (int) (24 * dp); 225 226 final Drawable stat_sys_adb = getContext() 227 .getDrawable(com.android.internal.R.drawable.stat_sys_adb); 228 if (!(stat_sys_adb instanceof VectorDrawable)) { 229 fail("stat_sys_adb is a " + stat_sys_adb.toString() 230 + ", not a VectorDrawable; stat_sys_adb malformed"); 231 } 232 233 if (stat_sys_adb.getIntrinsicWidth() != stat_sys_adb_width) { 234 fail("intrinsic width of stat_sys_adb is not 24dp; stat_sys_adb malformed"); 235 } 236 if (stat_sys_adb.getIntrinsicHeight() != stat_sys_adb_height) { 237 fail("intrinsic height of stat_sys_adb is not 24dp; stat_sys_adb malformed"); 238 } 239 final Bitmap referenceBitmap = Bitmap.createBitmap( 240 stat_sys_adb_width, 241 stat_sys_adb_height, 242 Bitmap.Config.ARGB_8888); 243 stat_sys_adb.setBounds(0, 0, stat_sys_adb_width, stat_sys_adb_height); 244 stat_sys_adb.draw(new Canvas(referenceBitmap)); 245 246 final Icon im1 = Icon.createWithResource(getContext(), 247 com.android.internal.R.drawable.stat_sys_adb); 248 final Drawable draw1 = im1.loadDrawable(getContext()); 249 250 assertEquals(stat_sys_adb.getIntrinsicWidth(), draw1.getIntrinsicWidth()); 251 assertEquals(stat_sys_adb.getIntrinsicHeight(), draw1.getIntrinsicHeight()); 252 assertEquals(im1.getResId(), com.android.internal.R.drawable.stat_sys_adb); 253 254 final Bitmap test1 = Bitmap.createBitmap( 255 draw1.getIntrinsicWidth(), 256 draw1.getIntrinsicHeight(), 257 Bitmap.Config.ARGB_8888); 258 draw1.setBounds(0, 0, test1.getWidth(), test1.getHeight()); 259 draw1.draw(new Canvas(test1)); 260 261 final File dir = getContext().getExternalFilesDir(null); 262 test1.compress(Bitmap.CompressFormat.PNG, 100, 263 new FileOutputStream(new File(dir, "testWithVectorDrawableResource-test.png"))); 264 if (!equalBitmaps(referenceBitmap, test1)) { 265 findBitmapDifferences(referenceBitmap, test1); 266 fail("testWithFile: file1 differs, check " + dir); 267 } 268 } 269 270 @Test testWithFile()271 public void testWithFile() throws Exception { 272 final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape)) 273 .getBitmap(); 274 final File dir = getContext().getExternalFilesDir(null); 275 final File file1 = new File(dir, "file1-original.png"); 276 bit1.compress(Bitmap.CompressFormat.PNG, 100, 277 new FileOutputStream(file1)); 278 279 final Icon im1 = Icon.createWithFilePath(file1.toString()); 280 final Drawable draw1 = im1.loadDrawable(mContext); 281 final Bitmap test1 = Bitmap.createBitmap(draw1.getIntrinsicWidth(), 282 draw1.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); 283 draw1.setBounds(0, 0, test1.getWidth(), test1.getHeight()); 284 draw1.draw(new Canvas(test1)); 285 286 test1.compress(Bitmap.CompressFormat.PNG, 100, 287 new FileOutputStream(new File(dir, "file1-test.png"))); 288 if (!equalBitmaps(bit1, test1)) { 289 findBitmapDifferences(bit1, test1); 290 fail("testWithFile: file1 differs, check " + dir); 291 } 292 } 293 294 @Test testWithAdaptiveIconResource_useMonochrome()295 public void testWithAdaptiveIconResource_useMonochrome() throws Exception { 296 final int colorMono = ((ColorDrawable) getContext().getDrawable( 297 android.R.color.system_accent2_800)).getColor(); 298 final Icon im1 = Icon.createWithResourceAdaptiveDrawable(getContext().getPackageName(), 299 R.drawable.adaptiveicon_drawable, true, 0.0f); 300 final Drawable draw1 = im1.loadDrawable(mContext); 301 assertThat(draw1 instanceof InsetDrawable).isTrue(); 302 ColorDrawable colorDrawable = (ColorDrawable) ((DrawableWrapper) draw1).getDrawable(); 303 assertThat(colorDrawable.getColor()).isEqualTo(colorMono); 304 } 305 306 @Test testWithAdaptiveIconResource_dontUseMonochrome()307 public void testWithAdaptiveIconResource_dontUseMonochrome() throws Exception { 308 final int colorMono = ((ColorDrawable) getContext().getDrawable( 309 android.R.color.system_accent2_800)).getColor(); 310 final int colorFg = ((ColorDrawable) getContext().getDrawable( 311 android.R.color.black)).getColor(); 312 final int colorBg = ((ColorDrawable) getContext().getDrawable( 313 android.R.color.white)).getColor(); 314 315 final Icon im1 = Icon.createWithResourceAdaptiveDrawable(getContext().getPackageName(), 316 R.drawable.adaptiveicon_drawable, false , 0.0f); 317 final Drawable draw1 = im1.loadDrawable(mContext); 318 assertThat(draw1 instanceof AdaptiveIconDrawable).isTrue(); 319 ColorDrawable colorDrawableMono = (ColorDrawable) ((AdaptiveIconDrawable) draw1) 320 .getMonochrome(); 321 assertThat(colorDrawableMono.getColor()).isEqualTo(colorMono); 322 ColorDrawable colorDrawableFg = (ColorDrawable) ((AdaptiveIconDrawable) draw1) 323 .getForeground(); 324 assertThat(colorDrawableFg.getColor()).isEqualTo(colorFg); 325 ColorDrawable colorDrawableBg = (ColorDrawable) ((AdaptiveIconDrawable) draw1) 326 .getBackground(); 327 assertThat(colorDrawableBg.getColor()).isEqualTo(colorBg); 328 } 329 330 @Test testAdaptiveIconResource_sameAs(@estParameter boolean useMonochrome)331 public void testAdaptiveIconResource_sameAs(@TestParameter boolean useMonochrome) 332 throws Exception { 333 final Icon im1 = Icon.createWithResourceAdaptiveDrawable(getContext().getPackageName(), 334 R.drawable.adaptiveicon_drawable, useMonochrome, 1.0f); 335 final Parcel parcel = Parcel.obtain(); 336 im1.writeToParcel(parcel, 0); 337 parcel.setDataPosition(0); 338 final Icon im2 = Icon.CREATOR.createFromParcel(parcel); 339 assertThat(im1.sameAs(im2)).isTrue(); 340 } 341 342 @Test testAsync()343 public void testAsync() throws Exception { 344 final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape)) 345 .getBitmap(); 346 final File dir = getContext().getExternalFilesDir(null); 347 final File file1 = new File(dir, "async-original.png"); 348 bit1.compress(Bitmap.CompressFormat.PNG, 100, 349 new FileOutputStream(file1)); 350 351 final Icon im1 = Icon.createWithFilePath(file1.toString()); 352 final HandlerThread thd = new HandlerThread("testAsync"); 353 thd.start(); 354 final Handler h = new Handler(thd.getLooper()); 355 L(TAG, "asyncTest: dispatching load to thread: " + thd); 356 im1.loadDrawableAsync(mContext, new Icon.OnDrawableLoadedListener() { 357 @Override 358 public void onDrawableLoaded(Drawable draw1) { 359 L(TAG, "asyncTest: thread: loading drawable"); 360 L(TAG, "asyncTest: thread: loaded: %dx%d", draw1.getIntrinsicWidth(), 361 draw1.getIntrinsicHeight()); 362 final Bitmap test1 = Bitmap.createBitmap(draw1.getIntrinsicWidth(), 363 draw1.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); 364 draw1.setBounds(0, 0, test1.getWidth(), test1.getHeight()); 365 draw1.draw(new Canvas(test1)); 366 367 try { 368 test1.compress(Bitmap.CompressFormat.PNG, 100, 369 new FileOutputStream(new File(dir, "async-test.png"))); 370 } catch (java.io.FileNotFoundException ex) { 371 fail("couldn't create test file: " + ex); 372 } 373 if (!equalBitmaps(bit1, test1)) { 374 findBitmapDifferences(bit1, test1); 375 fail("testAsync: file1 differs, check " + dir); 376 } 377 } 378 }, h); 379 L(TAG, "asyncTest: awaiting result"); 380 Thread.sleep(500); // ;_; 381 assertTrue("async-test.png does not exist!", new File(dir, "async-test.png").exists()); 382 L(TAG, "asyncTest: done"); 383 } 384 385 @Test testParcel()386 public void testParcel() throws Exception { 387 final Bitmap originalbits = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape)) 388 .getBitmap(); 389 390 final ByteArrayOutputStream ostream = new ByteArrayOutputStream( 391 originalbits.getWidth() * originalbits.getHeight() * 2); // guess 50% compression 392 originalbits.compress(Bitmap.CompressFormat.PNG, 100, ostream); 393 final byte[] pngdata = ostream.toByteArray(); 394 395 L("starting testParcel; bitmap: %d bytes, PNG: %d bytes", 396 originalbits.getByteCount(), 397 pngdata.length); 398 399 final File dir = getContext().getExternalFilesDir(null); 400 final File originalfile = new File(dir, "parcel-original.png"); 401 new FileOutputStream(originalfile).write(pngdata); 402 403 ArrayList<Icon> imgs = new ArrayList<>(); 404 final Icon file1 = Icon.createWithFilePath(originalfile.getAbsolutePath()); 405 imgs.add(file1); 406 final Icon bit1 = Icon.createWithBitmap(originalbits); 407 imgs.add(bit1); 408 final Icon data1 = Icon.createWithData(pngdata, 0, pngdata.length); 409 imgs.add(data1); 410 final Icon res1 = Icon.createWithResource(getContext(), R.drawable.landscape); 411 imgs.add(res1); 412 413 ArrayList<Icon> test = new ArrayList<>(); 414 final Parcel parcel = Parcel.obtain(); 415 int pos = 0; 416 parcel.writeInt(imgs.size()); 417 for (Icon img : imgs) { 418 img.writeToParcel(parcel, 0); 419 L("used %d bytes parceling: %s", parcel.dataPosition() - pos, img); 420 pos = parcel.dataPosition(); 421 } 422 423 parcel.setDataPosition(0); // rewind 424 final int N = parcel.readInt(); 425 for (int i=0; i<N; i++) { 426 Icon img = Icon.CREATOR.createFromParcel(parcel); 427 L("test %d: read from parcel: %s", i, img); 428 final File testfile = new File(dir, 429 String.format("parcel-test%02d.png", i)); 430 431 final Drawable draw1 = img.loadDrawable(mContext); 432 if (draw1 == null) { 433 fail("null drawable from img: " + img); 434 } 435 final Bitmap test1 = Bitmap.createBitmap(draw1.getIntrinsicWidth(), 436 draw1.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); 437 draw1.setBounds(0, 0, test1.getWidth(), test1.getHeight()); 438 draw1.draw(new Canvas(test1)); 439 440 try { 441 test1.compress(Bitmap.CompressFormat.PNG, 100, 442 new FileOutputStream(testfile)); 443 } catch (java.io.FileNotFoundException ex) { 444 fail("couldn't create test file " + testfile + ": " + ex); 445 } 446 if (!equalBitmaps(originalbits, test1)) { 447 findBitmapDifferences(originalbits, test1); 448 fail(testfile + " differs from original: " + originalfile); 449 } 450 451 } 452 } 453 getMaxWidth(int origWidth, int origHeight, int maxNumPixels)454 private int getMaxWidth(int origWidth, int origHeight, int maxNumPixels) { 455 float aspRatio = (float) origWidth / (float) origHeight; 456 int newHeight = (int) Math.sqrt(maxNumPixels / aspRatio); 457 return (int) (newHeight * aspRatio); 458 } 459 getMaxHeight(int origWidth, int origHeight, int maxNumPixels)460 private int getMaxHeight(int origWidth, int origHeight, int maxNumPixels) { 461 float aspRatio = (float) origWidth / (float) origHeight; 462 return (int) Math.sqrt(maxNumPixels / aspRatio); 463 } 464 465 @Test testScaleDownMaxSizeWithBitmap()466 public void testScaleDownMaxSizeWithBitmap() throws Exception { 467 final int bmpWidth = 13_000; 468 final int bmpHeight = 10_000; 469 final int bmpBpp = 4; 470 final int maxNumPixels = RecordingCanvas.MAX_BITMAP_SIZE / bmpBpp; 471 final int maxWidth = getMaxWidth(bmpWidth, bmpHeight, maxNumPixels); 472 final int maxHeight = getMaxHeight(bmpWidth, bmpHeight, maxNumPixels); 473 474 final Bitmap bm = Bitmap.createBitmap(bmpWidth, bmpHeight, Bitmap.Config.ARGB_8888); 475 final Icon ic = Icon.createWithBitmap(bm); 476 final Drawable drawable = ic.loadDrawable(mContext); 477 478 assertThat(Math.abs(drawable.getIntrinsicWidth() - maxWidth)).isLessThan(2); 479 assertThat(Math.abs(drawable.getIntrinsicHeight() - maxHeight)).isLessThan(2); 480 } 481 482 @Test testScaleDownMaxSizeWithAdaptiveBitmap()483 public void testScaleDownMaxSizeWithAdaptiveBitmap() throws Exception { 484 final int bmpWidth = 20_000; 485 final int bmpHeight = 10_000; 486 final int bmpBpp = 4; 487 final int maxNumPixels = RecordingCanvas.MAX_BITMAP_SIZE / bmpBpp; 488 final int maxWidth = getMaxWidth(bmpWidth, bmpHeight, maxNumPixels); 489 final int maxHeight = getMaxHeight(bmpWidth, bmpHeight, maxNumPixels); 490 491 final Bitmap bm = Bitmap.createBitmap(bmpWidth, bmpHeight, Bitmap.Config.ARGB_8888); 492 final Icon ic = Icon.createWithAdaptiveBitmap(bm); 493 final AdaptiveIconDrawable adaptiveDrawable = (AdaptiveIconDrawable) ic.loadDrawable( 494 mContext); 495 final Drawable drawable = adaptiveDrawable.getForeground(); 496 497 assertThat(drawable.getIntrinsicWidth()).isEqualTo(maxWidth); 498 assertThat(drawable.getIntrinsicHeight()).isEqualTo(maxHeight); 499 } 500 501 @Test testScaleDownMaxSizeWithResource()502 public void testScaleDownMaxSizeWithResource() throws Exception { 503 final Icon ic = Icon.createWithResource(getContext(), R.drawable.test_too_big); 504 final BitmapDrawable drawable = (BitmapDrawable) ic.loadDrawable(mContext); 505 506 assertThat(drawable.getBitmap().getByteCount()).isAtMost(RecordingCanvas.MAX_BITMAP_SIZE); 507 } 508 509 @Test testScaleDownMaxSizeWithFile()510 public void testScaleDownMaxSizeWithFile() throws Exception { 511 final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.test_too_big)) 512 .getBitmap(); 513 final File dir = getContext().getExternalFilesDir(null); 514 final File file1 = new File(dir, "file1-too-big.png"); 515 bit1.compress(Bitmap.CompressFormat.PNG, 100, 516 new FileOutputStream(file1)); 517 518 final Icon ic = Icon.createWithFilePath(file1.toString()); 519 final BitmapDrawable drawable = (BitmapDrawable) ic.loadDrawable(mContext); 520 521 assertThat(drawable.getBitmap().getByteCount()).isAtMost(RecordingCanvas.MAX_BITMAP_SIZE); 522 } 523 524 @Test testScaleDownMaxSizeWithData()525 public void testScaleDownMaxSizeWithData() throws Exception { 526 final int bmpBpp = 4; 527 final Bitmap originalBits = ((BitmapDrawable) getContext().getDrawable( 528 R.drawable.test_too_big)).getBitmap(); 529 final ByteArrayOutputStream ostream = new ByteArrayOutputStream( 530 originalBits.getWidth() * originalBits.getHeight() * bmpBpp); 531 originalBits.compress(Bitmap.CompressFormat.PNG, 100, ostream); 532 final byte[] pngdata = ostream.toByteArray(); 533 final Icon ic = Icon.createWithData(pngdata, 0, pngdata.length); 534 final BitmapDrawable drawable = (BitmapDrawable) ic.loadDrawable(mContext); 535 536 assertThat(drawable.getBitmap().getByteCount()).isAtMost(RecordingCanvas.MAX_BITMAP_SIZE); 537 } 538 539 @Test testLoadSafeDrawable_loadSuccessful()540 public void testLoadSafeDrawable_loadSuccessful() throws FileNotFoundException { 541 int uid = 12345; 542 String packageName = "test_pkg"; 543 544 final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape)) 545 .getBitmap(); 546 final File dir = getContext().getExternalFilesDir(null); 547 final File file1 = new File(dir, "file1-original.png"); 548 bit1.compress(Bitmap.CompressFormat.PNG, 100, new FileOutputStream(file1)); 549 550 final Icon im1 = Icon.createWithFilePath(file1.toString()); 551 552 TestableIUriGrantsManager ugm = 553 new TestableIUriGrantsManager(/* rejectCheckRequests */ false); 554 555 Drawable loadedDrawable = im1.loadDrawableCheckingUriGrant( 556 getContext(), ugm, uid, packageName); 557 assertThat(loadedDrawable).isNotNull(); 558 559 assertThat(ugm.mRequests.size()).isEqualTo(1); 560 TestableIUriGrantsManager.CheckRequest r = ugm.mRequests.get(0); 561 assertThat(r.mCallingUid).isEqualTo(uid); 562 assertThat(r.mPackageName).isEqualTo(packageName); 563 assertThat(r.mMode).isEqualTo(Intent.FLAG_GRANT_READ_URI_PERMISSION); 564 assertThat(r.mUri).isEqualTo(ContentProvider.getUriWithoutUserId(im1.getUri())); 565 assertThat(r.mUserId).isEqualTo(ContentProvider.getUserIdFromUri(im1.getUri())); 566 567 final Bitmap test1 = Bitmap.createBitmap(loadedDrawable.getIntrinsicWidth(), 568 loadedDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); 569 loadedDrawable.setBounds(0, 0, loadedDrawable.getIntrinsicWidth(), 570 loadedDrawable.getIntrinsicHeight()); 571 loadedDrawable.draw(new Canvas(test1)); 572 573 bit1.compress(Bitmap.CompressFormat.PNG, 100, 574 new FileOutputStream(new File(dir, "bitmap1-original.png"))); 575 test1.compress(Bitmap.CompressFormat.PNG, 100, 576 new FileOutputStream(new File(dir, "bitmap1-test.png"))); 577 if (!equalBitmaps(bit1, test1)) { 578 findBitmapDifferences(bit1, test1); 579 fail("bitmap1 differs, check " + dir); 580 } 581 } 582 583 @Test testLoadSafeDrawable_grantRejected_nullDrawable()584 public void testLoadSafeDrawable_grantRejected_nullDrawable() throws FileNotFoundException { 585 int uid = 12345; 586 String packageName = "test_pkg"; 587 588 final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape)) 589 .getBitmap(); 590 final File dir = getContext().getExternalFilesDir(null); 591 final File file1 = new File(dir, "file1-original.png"); 592 bit1.compress(Bitmap.CompressFormat.PNG, 100, new FileOutputStream(file1)); 593 594 final Icon im1 = Icon.createWithFilePath(file1.toString()); 595 596 TestableIUriGrantsManager ugm = 597 new TestableIUriGrantsManager(/* rejectCheckRequests */ true); 598 599 Drawable loadedDrawable = im1.loadDrawableCheckingUriGrant( 600 getContext(), ugm, uid, packageName); 601 602 assertThat(ugm.mRequests.size()).isEqualTo(1); 603 TestableIUriGrantsManager.CheckRequest r = ugm.mRequests.get(0); 604 assertThat(r.mCallingUid).isEqualTo(uid); 605 assertThat(r.mPackageName).isEqualTo(packageName); 606 assertThat(r.mMode).isEqualTo(Intent.FLAG_GRANT_READ_URI_PERMISSION); 607 assertThat(r.mUri).isEqualTo(ContentProvider.getUriWithoutUserId(im1.getUri())); 608 assertThat(r.mUserId).isEqualTo(ContentProvider.getUserIdFromUri(im1.getUri())); 609 610 assertThat(loadedDrawable).isNull(); 611 } 612 613 614 // ======== utils ======== 615 616 static final char[] GRADIENT = " .:;+=xX$#".toCharArray(); 617 static float[] hsv = new float[3]; colorToChar(int color)618 static char colorToChar(int color) { 619 int sum = ((color >> 16) & 0xff) 620 + ((color >> 8) & 0xff) 621 + ((color) & 0xff); 622 return GRADIENT[sum * (GRADIENT.length-1) / (3*0xff)]; 623 } printBits(int[] a, int w, int h)624 static void printBits(int[] a, int w, int h) { 625 final StringBuilder sb = new StringBuilder(); 626 for (int i=0; i<w; i++) { 627 for (int j=0; j<h; j++) { 628 sb.append(colorToChar(a[i+w*j])); 629 } 630 sb.append('\n'); 631 } 632 L(sb.toString()); 633 } printBits(Bitmap a)634 static void printBits(Bitmap a) { 635 final int w = a.getWidth(); 636 final int h = a.getHeight(); 637 int[] aPix = new int[w * h]; 638 printBits(aPix, w, h); 639 } equalBitmaps(Bitmap a, Bitmap b)640 boolean equalBitmaps(Bitmap a, Bitmap b) { 641 return equalBitmaps(a, b, null); 642 } 643 equalBitmaps(Bitmap a, Bitmap b, Region region)644 boolean equalBitmaps(Bitmap a, Bitmap b, Region region) { 645 if (a.getWidth() != b.getWidth() || a.getHeight() != b.getHeight()) return false; 646 647 final int w = a.getWidth(); 648 final int h = a.getHeight(); 649 int[] aPix = new int[w * h]; 650 int[] bPix = new int[w * h]; 651 652 if (region != null) { 653 for (int i = 0; i < w; i++) { 654 for (int j = 0; j < h; j++) { 655 if (region.contains(i, j) && a.getPixel(i, j) != b.getPixel(i, j)) { 656 return false; 657 } 658 } 659 } 660 return true; 661 } else { 662 a.getPixels(aPix, 0, w, 0, 0, w, h); 663 b.getPixels(bPix, 0, w, 0, 0, w, h); 664 return Arrays.equals(aPix, bPix); 665 } 666 } 667 findBitmapDifferences(Bitmap a, Bitmap b)668 void findBitmapDifferences(Bitmap a, Bitmap b) { 669 if (a.getWidth() != b.getWidth() || a.getHeight() != b.getHeight()) { 670 L("different sizes: %dx%d vs %dx%d", 671 a.getWidth(), a.getHeight(), b.getWidth(), b.getHeight()); 672 return; 673 } 674 675 final int w = a.getWidth(); 676 final int h = a.getHeight(); 677 int[] aPix = new int[w * h]; 678 int[] bPix = new int[w * h]; 679 680 a.getPixels(aPix, 0, w, 0, 0, w, h); 681 b.getPixels(bPix, 0, w, 0, 0, w, h); 682 683 L("bitmap a (%dx%d)", w, h); 684 printBits(aPix, w, h); 685 L("bitmap b (%dx%d)", w, h); 686 printBits(bPix, w, h); 687 688 StringBuffer sb = new StringBuffer("Different pixels: "); 689 for (int i=0; i<w; i++) { 690 for (int j=0; j<h; j++) { 691 if (aPix[i+w*j] != bPix[i+w*j]) { 692 sb.append(" ").append(i).append(",").append(j); 693 } 694 } 695 } 696 L(sb.toString()); 697 } 698 699 private static class TestableIUriGrantsManager extends IUriGrantsManager.Stub { 700 701 final ArrayList<CheckRequest> mRequests = new ArrayList<>(); 702 final boolean mRejectCheckRequests; 703 TestableIUriGrantsManager(boolean rejectCheckRequests)704 TestableIUriGrantsManager(boolean rejectCheckRequests) { 705 this.mRejectCheckRequests = rejectCheckRequests; 706 } 707 708 @Override takePersistableUriPermission(Uri uri, int i, String s, int i1)709 public void takePersistableUriPermission(Uri uri, int i, String s, int i1) 710 throws RemoteException { 711 712 } 713 714 @Override releasePersistableUriPermission(Uri uri, int i, String s, int i1)715 public void releasePersistableUriPermission(Uri uri, int i, String s, int i1) 716 throws RemoteException { 717 718 } 719 720 @Override grantUriPermissionFromOwner(IBinder iBinder, int i, String s, Uri uri, int i1, int i2, int i3)721 public void grantUriPermissionFromOwner(IBinder iBinder, int i, String s, Uri uri, int i1, 722 int i2, int i3) throws RemoteException { 723 724 } 725 726 @Override getGrantedUriPermissions(String s, int i)727 public ParceledListSlice getGrantedUriPermissions(String s, int i) throws RemoteException { 728 return null; 729 } 730 731 @Override clearGrantedUriPermissions(String s, int i)732 public void clearGrantedUriPermissions(String s, int i) throws RemoteException { 733 734 } 735 736 @Override getUriPermissions(String s, boolean b, boolean b1)737 public ParceledListSlice getUriPermissions(String s, boolean b, boolean b1) 738 throws RemoteException { 739 return null; 740 } 741 742 @Override checkGrantUriPermission_ignoreNonSystem( int uid, String packageName, Uri uri, int mode, int userId)743 public int checkGrantUriPermission_ignoreNonSystem( 744 int uid, String packageName, Uri uri, int mode, int userId) 745 throws RemoteException { 746 CheckRequest r = new CheckRequest(uid, packageName, uri, mode, userId); 747 mRequests.add(r); 748 if (mRejectCheckRequests) { 749 throw new SecurityException(); 750 } else { 751 return uid; 752 } 753 } 754 755 static class CheckRequest { 756 final int mCallingUid; 757 final String mPackageName; 758 final Uri mUri; 759 final int mMode; 760 final int mUserId; 761 CheckRequest(int callingUid, String packageName, Uri uri, int mode, int userId)762 CheckRequest(int callingUid, String packageName, Uri uri, int mode, int userId) { 763 this.mCallingUid = callingUid; 764 this.mPackageName = packageName; 765 this.mUri = uri; 766 this.mMode = mode; 767 this.mUserId = userId; 768 } 769 } 770 } 771 }