1 /* 2 * Copyright (C) 2008 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.cts; 18 19 import android.content.res.Resources.Theme; 20 import android.graphics.Outline; 21 import android.graphics.cts.R; 22 23 import org.xmlpull.v1.XmlPullParser; 24 import org.xmlpull.v1.XmlPullParserException; 25 26 import android.content.res.Resources; 27 import android.content.res.XmlResourceParser; 28 import android.graphics.Bitmap; 29 import android.graphics.BitmapFactory; 30 import android.graphics.Canvas; 31 import android.graphics.Color; 32 import android.graphics.ColorFilter; 33 import android.graphics.NinePatch; 34 import android.graphics.Paint; 35 import android.graphics.PixelFormat; 36 import android.graphics.Rect; 37 import android.graphics.Region; 38 import android.graphics.Bitmap.Config; 39 import android.graphics.PorterDuff.Mode; 40 import android.graphics.drawable.Drawable; 41 import android.graphics.drawable.NinePatchDrawable; 42 import android.graphics.drawable.Drawable.ConstantState; 43 import android.test.InstrumentationTestCase; 44 import android.util.AttributeSet; 45 import android.util.DisplayMetrics; 46 import android.util.Xml; 47 48 import java.io.File; 49 import java.io.FileOutputStream; 50 import java.io.IOException; 51 52 public class NinePatchDrawableTest extends InstrumentationTestCase { 53 // A small value is actually making sure that the values are matching 54 // exactly with the golden image. 55 // We can increase the threshold if the Skia is drawing with some variance 56 // on different devices. So far, the tests show they are matching correctly. 57 private static final float PIXEL_ERROR_THRESHOLD = 0.03f; 58 private static final float PIXEL_ERROR_COUNT_THRESHOLD = 0.005f; 59 60 private static final int MIN_CHUNK_SIZE = 32; 61 62 // Set true to generate golden images, false for normal tests. 63 private static final boolean DBG_DUMP_PNG = false; 64 65 private NinePatchDrawable mNinePatchDrawable; 66 67 private Resources mResources; 68 69 @Override setUp()70 protected void setUp() throws Exception { 71 super.setUp(); 72 mResources = getInstrumentation().getTargetContext().getResources(); 73 mNinePatchDrawable = getNinePatchDrawable(R.drawable.ninepatch_0); 74 } 75 76 @SuppressWarnings("deprecation") testConstructors()77 public void testConstructors() { 78 byte[] chunk = new byte[MIN_CHUNK_SIZE]; 79 chunk[MIN_CHUNK_SIZE - 1] = 1; 80 81 Rect r = new Rect(); 82 83 Bitmap bmp = BitmapFactory.decodeResource(mResources, R.drawable.ninepatch_0); 84 String name = mResources.getResourceName(R.drawable.ninepatch_0); 85 86 new NinePatchDrawable(bmp, chunk, r, name); 87 88 new NinePatchDrawable(new NinePatch(bmp, chunk, name)); 89 90 chunk = new byte[MIN_CHUNK_SIZE - 1]; 91 chunk[MIN_CHUNK_SIZE - 2] = 1; 92 try { 93 new NinePatchDrawable(bmp, chunk, r, name); 94 fail("The constructor should check whether the chunk is illegal."); 95 } catch (RuntimeException e) { 96 // This exception is thrown by native method. 97 } 98 } 99 testDraw()100 public void testDraw() { 101 Bitmap bmp = Bitmap.createBitmap(9, 9, Config.ARGB_8888); 102 Canvas c = new Canvas(bmp); 103 104 int ocean = Color.rgb(0, 0xFF, 0x80); 105 106 mNinePatchDrawable.setBounds(0, 0, 9, 9); 107 mNinePatchDrawable.draw(c); 108 assertColorFillRect(bmp, 0, 0, 4, 4, Color.RED); 109 assertColorFillRect(bmp, 5, 0, 4, 4, Color.BLUE); 110 assertColorFillRect(bmp, 0, 5, 4, 4, ocean); 111 assertColorFillRect(bmp, 5, 5, 4, 4, Color.YELLOW); 112 assertColorFillRect(bmp, 4, 0, 1, 9, Color.WHITE); 113 assertColorFillRect(bmp, 0, 4, 9, 1, Color.WHITE); 114 115 bmp.eraseColor(0xff000000); 116 117 mNinePatchDrawable.setBounds(0, 0, 3, 3); 118 mNinePatchDrawable.draw(c); 119 assertColorFillRect(bmp, 0, 0, 1, 1, Color.RED); 120 assertColorFillRect(bmp, 2, 0, 1, 1, Color.BLUE); 121 assertColorFillRect(bmp, 0, 2, 1, 1, ocean); 122 assertColorFillRect(bmp, 2, 2, 1, 1, Color.YELLOW); 123 assertColorFillRect(bmp, 1, 0, 1, 3, Color.WHITE); 124 assertColorFillRect(bmp, 0, 1, 3, 1, Color.WHITE); 125 126 try { 127 mNinePatchDrawable.draw(null); 128 fail("The method should check whether the canvas is null."); 129 } catch (NullPointerException e) { 130 // expected 131 } 132 } 133 testGetChangingConfigurations()134 public void testGetChangingConfigurations() { 135 ConstantState constantState = mNinePatchDrawable.getConstantState(); 136 137 // default 138 assertEquals(0, constantState.getChangingConfigurations()); 139 assertEquals(0, mNinePatchDrawable.getChangingConfigurations()); 140 141 // change the drawable's configuration does not affect the state's configuration 142 mNinePatchDrawable.setChangingConfigurations(0xff); 143 assertEquals(0xff, mNinePatchDrawable.getChangingConfigurations()); 144 assertEquals(0, constantState.getChangingConfigurations()); 145 146 // the state's configuration get refreshed 147 constantState = mNinePatchDrawable.getConstantState(); 148 assertEquals(0xff, constantState.getChangingConfigurations()); 149 150 // set a new configuration to drawable 151 mNinePatchDrawable.setChangingConfigurations(0xff00); 152 assertEquals(0xff, constantState.getChangingConfigurations()); 153 assertEquals(0xffff, mNinePatchDrawable.getChangingConfigurations()); 154 } 155 testGetPadding()156 public void testGetPadding() { 157 Rect r = new Rect(); 158 NinePatchDrawable npd = (NinePatchDrawable) mResources.getDrawable(R.drawable.ninepatch_0); 159 assertTrue(npd.getPadding(r)); 160 // exact padding unknown due to possible density scaling 161 assertEquals(0, r.left); 162 assertEquals(0, r.top); 163 assertTrue(r.right > 0); 164 assertTrue(r.bottom > 0); 165 166 npd = (NinePatchDrawable) mResources.getDrawable(R.drawable.ninepatch_1); 167 assertTrue(npd.getPadding(r)); 168 assertTrue(r.left > 0); 169 assertTrue(r.top > 0); 170 assertTrue(r.right > 0); 171 assertTrue(r.bottom > 0); 172 } 173 testSetAlpha()174 public void testSetAlpha() { 175 assertEquals(0xff, mNinePatchDrawable.getPaint().getAlpha()); 176 177 mNinePatchDrawable.setAlpha(0); 178 assertEquals(0, mNinePatchDrawable.getPaint().getAlpha()); 179 180 mNinePatchDrawable.setAlpha(-1); 181 assertEquals(0xff, mNinePatchDrawable.getPaint().getAlpha()); 182 183 mNinePatchDrawable.setAlpha(0xfffe); 184 assertEquals(0xfe, mNinePatchDrawable.getPaint().getAlpha()); 185 } 186 testSetColorFilter()187 public void testSetColorFilter() { 188 assertNull(mNinePatchDrawable.getPaint().getColorFilter()); 189 190 MockColorFilter cf = new MockColorFilter(); 191 mNinePatchDrawable.setColorFilter(cf); 192 assertSame(cf, mNinePatchDrawable.getPaint().getColorFilter()); 193 194 mNinePatchDrawable.setColorFilter(null); 195 assertNull(mNinePatchDrawable.getPaint().getColorFilter()); 196 } 197 testSetTint()198 public void testSetTint() { 199 mNinePatchDrawable.setTint(Color.BLACK); 200 mNinePatchDrawable.setTintMode(Mode.SRC_OVER); 201 assertEquals("Nine-patch is tinted", Color.BLACK, 202 DrawableTestUtils.getPixel(mNinePatchDrawable, 0, 0)); 203 204 mNinePatchDrawable.setTintList(null); 205 mNinePatchDrawable.setTintMode(null); 206 } 207 testSetDither()208 public void testSetDither() { 209 mNinePatchDrawable.setDither(false); 210 assertFalse(mNinePatchDrawable.getPaint().isDither()); 211 212 mNinePatchDrawable.setDither(true); 213 assertTrue(mNinePatchDrawable.getPaint().isDither()); 214 } 215 testSetFilterBitmap()216 public void testSetFilterBitmap() { 217 mNinePatchDrawable.setFilterBitmap(false); 218 assertFalse(mNinePatchDrawable.getPaint().isFilterBitmap()); 219 220 mNinePatchDrawable.setFilterBitmap(true); 221 assertTrue(mNinePatchDrawable.getPaint().isFilterBitmap()); 222 } 223 testIsFilterBitmap()224 public void testIsFilterBitmap() { 225 mNinePatchDrawable.setFilterBitmap(false); 226 assertFalse(mNinePatchDrawable.isFilterBitmap()); 227 assertEquals(mNinePatchDrawable.isFilterBitmap(), 228 mNinePatchDrawable.getPaint().isFilterBitmap()); 229 230 231 mNinePatchDrawable.setFilterBitmap(true); 232 assertTrue(mNinePatchDrawable.isFilterBitmap()); 233 assertEquals(mNinePatchDrawable.isFilterBitmap(), 234 mNinePatchDrawable.getPaint().isFilterBitmap()); 235 } 236 testGetPaint()237 public void testGetPaint() { 238 Paint paint = mNinePatchDrawable.getPaint(); 239 assertNotNull(paint); 240 241 assertSame(paint, mNinePatchDrawable.getPaint()); 242 } 243 testGetIntrinsicWidth()244 public void testGetIntrinsicWidth() { 245 Bitmap bmp = getBitmapUnscaled(R.drawable.ninepatch_0); 246 assertEquals(bmp.getWidth(), mNinePatchDrawable.getIntrinsicWidth()); 247 assertEquals(5, mNinePatchDrawable.getIntrinsicWidth()); 248 249 mNinePatchDrawable = getNinePatchDrawable(R.drawable.ninepatch_1); 250 bmp = getBitmapUnscaled(R.drawable.ninepatch_1); 251 assertEquals(bmp.getWidth(), mNinePatchDrawable.getIntrinsicWidth()); 252 assertEquals(9, mNinePatchDrawable.getIntrinsicWidth()); 253 } 254 testGetMinimumWidth()255 public void testGetMinimumWidth() { 256 Bitmap bmp = getBitmapUnscaled(R.drawable.ninepatch_0); 257 assertEquals(bmp.getWidth(), mNinePatchDrawable.getMinimumWidth()); 258 assertEquals(5, mNinePatchDrawable.getMinimumWidth()); 259 260 mNinePatchDrawable = getNinePatchDrawable(R.drawable.ninepatch_1); 261 bmp = getBitmapUnscaled(R.drawable.ninepatch_1); 262 assertEquals(bmp.getWidth(), mNinePatchDrawable.getMinimumWidth()); 263 assertEquals(9, mNinePatchDrawable.getMinimumWidth()); 264 } 265 testGetIntrinsicHeight()266 public void testGetIntrinsicHeight() { 267 Bitmap bmp = getBitmapUnscaled(R.drawable.ninepatch_0); 268 assertEquals(bmp.getHeight(), mNinePatchDrawable.getIntrinsicHeight()); 269 assertEquals(5, mNinePatchDrawable.getIntrinsicHeight()); 270 271 mNinePatchDrawable = getNinePatchDrawable(R.drawable.ninepatch_1); 272 bmp = getBitmapUnscaled(R.drawable.ninepatch_1); 273 assertEquals(bmp.getHeight(), mNinePatchDrawable.getIntrinsicHeight()); 274 assertEquals(9, mNinePatchDrawable.getIntrinsicHeight()); 275 } 276 testGetMinimumHeight()277 public void testGetMinimumHeight() { 278 Bitmap bmp = getBitmapUnscaled(R.drawable.ninepatch_0); 279 assertEquals(bmp.getHeight(), mNinePatchDrawable.getMinimumHeight()); 280 assertEquals(5, mNinePatchDrawable.getMinimumHeight()); 281 282 mNinePatchDrawable = getNinePatchDrawable(R.drawable.ninepatch_1); 283 bmp = getBitmapUnscaled(R.drawable.ninepatch_1); 284 assertEquals(bmp.getHeight(), mNinePatchDrawable.getMinimumHeight()); 285 assertEquals(9, mNinePatchDrawable.getMinimumHeight()); 286 } 287 288 // Known failure: Bug 2834281 - Bitmap#hasAlpha seems to return true for 289 // images without alpha suppress_testGetOpacity()290 public void suppress_testGetOpacity() { 291 assertEquals(PixelFormat.OPAQUE, mNinePatchDrawable.getOpacity()); 292 293 mNinePatchDrawable = getNinePatchDrawable(R.drawable.ninepatch_1); 294 assertEquals(PixelFormat.TRANSLUCENT, mNinePatchDrawable.getOpacity()); 295 } 296 testGetTransparentRegion()297 public void testGetTransparentRegion() { 298 // opaque image 299 Region r = mNinePatchDrawable.getTransparentRegion(); 300 assertNull(r); 301 302 mNinePatchDrawable.setBounds(0, 0, 7, 7); 303 r = mNinePatchDrawable.getTransparentRegion(); 304 assertNull(r); 305 306 // translucent image 307 mNinePatchDrawable = getNinePatchDrawable(R.drawable.ninepatch_1); 308 r = mNinePatchDrawable.getTransparentRegion(); 309 assertNull(r); 310 311 mNinePatchDrawable.setBounds(1, 1, 7, 7); 312 r = mNinePatchDrawable.getTransparentRegion(); 313 assertNotNull(r); 314 assertEquals(new Rect(1, 1, 7, 7), r.getBounds()); 315 } 316 testGetConstantState()317 public void testGetConstantState() { 318 assertNotNull(mNinePatchDrawable.getConstantState()); 319 320 ConstantState constantState = mNinePatchDrawable.getConstantState(); 321 // change the drawable's configuration does not affect the state's configuration 322 mNinePatchDrawable.setChangingConfigurations(0xff); 323 assertEquals(0, constantState.getChangingConfigurations()); 324 // the state's configuration refreshed when getConstantState is called. 325 constantState = mNinePatchDrawable.getConstantState(); 326 assertEquals(0xff, constantState.getChangingConfigurations()); 327 } 328 testInflate()329 public void testInflate() throws XmlPullParserException, IOException { 330 int sourceWidth = 80; 331 int sourceHeight = 120; 332 int[] colors = new int[sourceWidth * sourceHeight]; 333 Bitmap bitmap = Bitmap.createBitmap( 334 colors, sourceWidth, sourceHeight, Bitmap.Config.RGB_565); 335 NinePatchDrawable ninePatchDrawable = new NinePatchDrawable( 336 mResources, bitmap, new byte[1000], null, "TESTNAME"); 337 338 int sourceDensity = bitmap.getDensity(); 339 int targetDensity = mResources.getDisplayMetrics().densityDpi; 340 int targetWidth = DrawableTestUtils.scaleBitmapFromDensity( 341 sourceWidth, sourceDensity, targetDensity); 342 int targetHeight = DrawableTestUtils.scaleBitmapFromDensity( 343 sourceHeight, sourceDensity, targetDensity); 344 assertEquals(targetWidth, ninePatchDrawable.getIntrinsicWidth()); 345 assertEquals(targetHeight, ninePatchDrawable.getIntrinsicHeight()); 346 347 XmlResourceParser parser = mResources.getXml(R.drawable.ninepatchdrawable); 348 int type; 349 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 350 && type != XmlPullParser.START_TAG) { 351 } 352 AttributeSet attrs = Xml.asAttributeSet(parser); 353 ninePatchDrawable.inflate(mResources, parser, attrs); 354 355 assertTrue(ninePatchDrawable.getPaint().isDither()); 356 assertTrue(sourceHeight != ninePatchDrawable.getIntrinsicHeight()); 357 assertTrue(sourceWidth != ninePatchDrawable.getIntrinsicWidth()); 358 } 359 testMutate()360 public void testMutate() { 361 NinePatchDrawable d1 = 362 (NinePatchDrawable) mResources.getDrawable(R.drawable.ninepatchdrawable); 363 NinePatchDrawable d2 = 364 (NinePatchDrawable) mResources.getDrawable(R.drawable.ninepatchdrawable); 365 NinePatchDrawable d3 = 366 (NinePatchDrawable) mResources.getDrawable(R.drawable.ninepatchdrawable); 367 368 // the state is not shared before mutate. 369 d1.setDither(false); 370 assertFalse(d1.getPaint().isDither()); 371 assertTrue(d2.getPaint().isDither()); 372 assertTrue(d3.getPaint().isDither()); 373 374 // cannot test if mutate worked, since state was not shared before 375 d1.mutate(); 376 } 377 378 private static final int[] DENSITY_VALUES = new int[] { 379 160, 80, 320 380 }; 381 382 private static final int[] DENSITY_IMAGES = new int[] { 383 R.drawable.nine_patch_density 384 }; 385 386 private static final int[][] DENSITY_GOLDEN_IMAGES = new int[][] { 387 { 388 R.drawable.nine_patch_density_golden_160, 389 R.drawable.nine_patch_density_golden_80, 390 R.drawable.nine_patch_density_golden_320, 391 } 392 }; 393 394 private interface TargetDensitySetter { setTargetDensity(NinePatchDrawable dr, int density)395 void setTargetDensity(NinePatchDrawable dr, int density); 396 } 397 testSetTargetDensityOuter(TargetDensitySetter densitySetter)398 private void testSetTargetDensityOuter(TargetDensitySetter densitySetter) { 399 final Resources res = mResources; 400 final int densityDpi = res.getConfiguration().densityDpi; 401 try { 402 testSetTargetDensityInner(res, DENSITY_IMAGES[0], DENSITY_VALUES, densitySetter); 403 } catch (IOException | XmlPullParserException e) { 404 throw new RuntimeException(e); 405 } finally { 406 DrawableTestUtils.setResourcesDensity(res, densityDpi); 407 } 408 } 409 testSetTargetDensityInner(Resources res, int sourceResId, int[] densities, TargetDensitySetter densitySetter)410 private void testSetTargetDensityInner(Resources res, int sourceResId, int[] densities, 411 TargetDensitySetter densitySetter) throws XmlPullParserException, IOException { 412 final Rect tempPadding = new Rect(); 413 414 // Capture initial state at preload density. 415 final int preloadDensityDpi = densities[0]; 416 DrawableTestUtils.setResourcesDensity(res, preloadDensityDpi); 417 418 final NinePatchDrawable preloadedDrawable = 419 (NinePatchDrawable) res.getDrawable(sourceResId).mutate(); 420 final int origWidth = preloadedDrawable.getIntrinsicWidth(); 421 final int origHeight = preloadedDrawable.getIntrinsicHeight(); 422 final Rect origPadding = new Rect(); 423 preloadedDrawable.getPadding(origPadding); 424 425 for (int i = 1; i < densities.length; i++) { 426 final int scaledDensityDpi = densities[i]; 427 final float scale = scaledDensityDpi / (float) preloadDensityDpi; 428 429 final NinePatchDrawable scaledDrawable = 430 (NinePatchDrawable) res.getDrawable(sourceResId).mutate(); 431 densitySetter.setTargetDensity(scaledDrawable, scaledDensityDpi); 432 433 // Sizes are rounded. 434 assertEquals(Math.round(origWidth * scale), scaledDrawable.getIntrinsicWidth()); 435 assertEquals(Math.round(origHeight * scale), scaledDrawable.getIntrinsicHeight()); 436 437 // Padding is truncated. 438 assertTrue(scaledDrawable.getPadding(tempPadding)); 439 assertEquals((int) (origPadding.left * scale), tempPadding.left); 440 assertEquals((int) (origPadding.top * scale), tempPadding.top); 441 assertEquals((int) (origPadding.right * scale), tempPadding.right); 442 assertEquals((int) (origPadding.bottom * scale), tempPadding.bottom); 443 444 // Ensure theme density is applied correctly. Unlike most 445 // drawables, we don't have any loss of accuracy because density 446 // changes are re-computed from the source every time. 447 DrawableTestUtils.setResourcesDensity(res, preloadDensityDpi); 448 449 final Theme t = res.newTheme(); 450 scaledDrawable.applyTheme(t); 451 assertEquals(origWidth, scaledDrawable.getIntrinsicWidth()); 452 assertEquals(origHeight, scaledDrawable.getIntrinsicHeight()); 453 assertTrue(scaledDrawable.getPadding(tempPadding)); 454 assertEquals(origPadding, tempPadding); 455 } 456 } 457 testSetTargetDensity()458 public void testSetTargetDensity() { 459 testSetTargetDensityOuter(new TargetDensitySetter() { 460 @Override 461 public void setTargetDensity(NinePatchDrawable dr, int density) { 462 dr.setTargetDensity(density); 463 } 464 }); 465 } 466 testSetTargetDensity_Canvas()467 public void testSetTargetDensity_Canvas() { 468 // This should be identical to calling setTargetDensity(int) with the 469 // value returned by Canvas.getDensity(). 470 testSetTargetDensityOuter(new TargetDensitySetter() { 471 @Override 472 public void setTargetDensity(NinePatchDrawable dr, int density) { 473 Canvas c = new Canvas(); 474 c.setDensity(density); 475 dr.setTargetDensity(c); 476 } 477 }); 478 } 479 testSetTargetDensity_DisplayMetrics()480 public void testSetTargetDensity_DisplayMetrics() { 481 // This should be identical to calling setTargetDensity(int) with the 482 // value of DisplayMetrics.densityDpi. 483 testSetTargetDensityOuter(new TargetDensitySetter() { 484 @Override 485 public void setTargetDensity(NinePatchDrawable dr, int density) { 486 DisplayMetrics dm = new DisplayMetrics(); 487 dm.densityDpi = density; 488 dr.setTargetDensity(dm); 489 } 490 }); 491 } 492 testPreloadDensity()493 public void testPreloadDensity() throws XmlPullParserException, IOException { 494 final Resources res = mResources; 495 final int densityDpi = res.getConfiguration().densityDpi; 496 try { 497 testPreloadDensityInner(res, DENSITY_IMAGES[0], DENSITY_VALUES, 498 DENSITY_GOLDEN_IMAGES[0]); 499 } finally { 500 DrawableTestUtils.setResourcesDensity(res, densityDpi); 501 } 502 } 503 testPreloadDensityInner(Resources res, int sourceResId, int[] densities, int[] goldenResIds)504 private void testPreloadDensityInner(Resources res, int sourceResId, int[] densities, 505 int[] goldenResIds) throws XmlPullParserException, IOException { 506 // Capture initial state at preload density. 507 final int preloadDensityDpi = densities[0]; 508 final NinePatchDrawable preloadedDrawable = preloadedDrawable(res, 509 densities[0], sourceResId); 510 511 final ConstantState preloadedConstantState = preloadedDrawable.getConstantState(); 512 final int origWidth = preloadedDrawable.getIntrinsicWidth(); 513 final int origHeight = preloadedDrawable.getIntrinsicHeight(); 514 final Rect origPadding = new Rect(); 515 preloadedDrawable.getPadding(origPadding); 516 517 compareOrSave(preloadedDrawable, preloadDensityDpi, sourceResId, goldenResIds[0]); 518 519 for (int i = 1; i < densities.length; i++) { 520 final int scaledDensityDpi = densities[i]; 521 final float scale = scaledDensityDpi / (float) preloadDensityDpi; 522 DrawableTestUtils.setResourcesDensity(res, scaledDensityDpi); 523 524 final NinePatchDrawable scaledDrawable = 525 (NinePatchDrawable) preloadedConstantState.newDrawable(res); 526 527 assertEquals(Math.round(origWidth * scale), scaledDrawable.getIntrinsicWidth()); 528 assertEquals(Math.round(origHeight * scale), scaledDrawable.getIntrinsicHeight()); 529 530 // Padding is truncated. 531 final Rect tempPadding = new Rect(); 532 assertTrue(scaledDrawable.getPadding(tempPadding)); 533 assertEquals((int) (origPadding.left * scale), tempPadding.left); 534 assertEquals((int) (origPadding.top * scale), tempPadding.top); 535 assertEquals((int) (origPadding.right * scale), tempPadding.right); 536 assertEquals((int) (origPadding.bottom * scale), tempPadding.bottom); 537 538 compareOrSave(scaledDrawable, scaledDensityDpi, sourceResId, goldenResIds[i]); 539 540 // Ensure theme density is applied correctly. Unlike most 541 // drawables, we don't have any loss of accuracy because density 542 // changes are re-computed from the source every time. 543 DrawableTestUtils.setResourcesDensity(res, preloadDensityDpi); 544 545 final Theme t = res.newTheme(); 546 scaledDrawable.applyTheme(t); 547 assertEquals(origWidth, scaledDrawable.getIntrinsicWidth()); 548 assertEquals(origHeight, scaledDrawable.getIntrinsicHeight()); 549 assertTrue(scaledDrawable.getPadding(tempPadding)); 550 assertEquals(origPadding, tempPadding); 551 } 552 } 553 preloadedDrawable(Resources res, int densityDpi, int sourceResId)554 private static NinePatchDrawable preloadedDrawable(Resources res, int densityDpi, int sourceResId) 555 throws XmlPullParserException, IOException { 556 DrawableTestUtils.setResourcesDensity(res, densityDpi); 557 final XmlResourceParser parser = DrawableTestUtils.getResourceParser(res, sourceResId); 558 final NinePatchDrawable preloadedDrawable = new NinePatchDrawable(null); 559 preloadedDrawable.inflate(res, parser, Xml.asAttributeSet(parser)); 560 return preloadedDrawable; 561 } 562 testOutlinePreloadDensity()563 public void testOutlinePreloadDensity() throws XmlPullParserException, IOException { 564 final Resources res = mResources; 565 final int densityDpi = res.getConfiguration().densityDpi; 566 try { 567 testOutlinePreloadDensityInner(res); 568 } finally { 569 DrawableTestUtils.setResourcesDensity(res, densityDpi); 570 } 571 } 572 testOutlinePreloadDensityInner(Resources res)573 private static void testOutlinePreloadDensityInner(Resources res) 574 throws XmlPullParserException, IOException { 575 // Capture initial state at preload density. 576 final int preloadDensityDpi = DENSITY_VALUES[0]; 577 final NinePatchDrawable preloadedDrawable = preloadedDrawable(res, preloadDensityDpi, 578 R.drawable.nine_patch_odd_insets); 579 580 final ConstantState preloadedConstantState = preloadedDrawable.getConstantState(); 581 final int bound = 40; 582 final int expectedInset = 5; 583 preloadedDrawable.setBounds(0, 0, bound, bound); 584 final Outline origOutline = new Outline(); 585 preloadedDrawable.getOutline(origOutline); 586 final Rect origOutlineRect = new Rect(); 587 origOutline.getRect(origOutlineRect); 588 assertEquals(new Rect(expectedInset, expectedInset, bound - expectedInset, 589 bound - expectedInset), origOutlineRect); 590 final float origOutlineRadius = origOutline.getRadius(); 591 float expectedRadius = 6.8f; 592 assertEquals(expectedRadius, origOutlineRadius, 0.1f); 593 for (int i = 1; i < DENSITY_VALUES.length; i++) { 594 final int scaledDensityDpi = DENSITY_VALUES[i]; 595 final float scale = scaledDensityDpi / (float) preloadDensityDpi; 596 DrawableTestUtils.setResourcesDensity(res, scaledDensityDpi); 597 final NinePatchDrawable scaledDrawable = 598 (NinePatchDrawable) preloadedConstantState.newDrawable(res); 599 600 int scaledBound = (int) (bound * scale); 601 scaledDrawable.setBounds(0, 0, scaledBound, scaledBound); 602 603 final Outline tempOutline = new Outline(); 604 scaledDrawable.getOutline(tempOutline); 605 final Rect tempOutlineRect = new Rect(); 606 assertTrue(tempOutline.getRect(tempOutlineRect)); 607 assertEquals((int) Math.ceil(origOutlineRect.left * scale), tempOutlineRect.left); 608 assertEquals((int) Math.ceil(origOutlineRect.top * scale), tempOutlineRect.top); 609 assertEquals((int) Math.floor(origOutlineRect.right * scale), tempOutlineRect.right); 610 assertEquals((int) Math.floor(origOutlineRect.bottom * scale), tempOutlineRect.bottom); 611 assertEquals(origOutlineRadius * scale, tempOutline.getRadius(), 0.1f); 612 } 613 } 614 assertColorFillRect(Bitmap bmp, int x, int y, int w, int h, int color)615 private void assertColorFillRect(Bitmap bmp, int x, int y, int w, int h, int color) { 616 for (int i = x; i < x + w; i++) { 617 for (int j = y; j < y + h; j++) { 618 assertEquals(color, bmp.getPixel(i, j)); 619 } 620 } 621 } 622 getNinePatchDrawable(int resId)623 private NinePatchDrawable getNinePatchDrawable(int resId) { 624 // jump through hoops to avoid scaling the tiny ninepatch, which would skew the results 625 // depending on device density 626 Bitmap bitmap = getBitmapUnscaled(resId); 627 NinePatch np = new NinePatch(bitmap, bitmap.getNinePatchChunk(), null); 628 return new NinePatchDrawable(mResources, np); 629 } 630 getBitmapUnscaled(int resId)631 private Bitmap getBitmapUnscaled(int resId) { 632 BitmapFactory.Options opts = new BitmapFactory.Options(); 633 opts.inDensity = opts.inTargetDensity = mResources.getDisplayMetrics().densityDpi; 634 Bitmap bitmap = BitmapFactory.decodeResource(mResources, resId, opts); 635 return bitmap; 636 } 637 compareOrSave(Drawable dr, int densityDpi, int sourceResId, int goldenResId)638 private void compareOrSave(Drawable dr, int densityDpi, int sourceResId, int goldenResId) { 639 final int width = dr.getIntrinsicWidth(); 640 final int height = dr.getIntrinsicHeight(); 641 final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 642 bitmap.setDensity(0); 643 644 final Canvas canvas = new Canvas(bitmap); 645 dr.setBounds(0, 0, width, height); 646 dr.draw(canvas); 647 648 if (DBG_DUMP_PNG) { 649 saveGoldenImage(bitmap, sourceResId, densityDpi); 650 } else { 651 final Bitmap golden = BitmapFactory.decodeResource(mResources, goldenResId); 652 DrawableTestUtils.compareImages(densityDpi + " dpi", golden, bitmap, 653 PIXEL_ERROR_THRESHOLD, PIXEL_ERROR_COUNT_THRESHOLD, 0 /* tolerance */); 654 } 655 } 656 saveGoldenImage(Bitmap bitmap, int sourceResId, int densityDpi)657 private void saveGoldenImage(Bitmap bitmap, int sourceResId, int densityDpi) { 658 // Save the image to the disk. 659 FileOutputStream out = null; 660 661 try { 662 final String outputFolder = "/sdcard/temp/"; 663 final File folder = new File(outputFolder); 664 if (!folder.exists()) { 665 folder.mkdir(); 666 } 667 668 final String sourceFilename = new File(mResources.getString(sourceResId)).getName(); 669 final String sourceTitle = sourceFilename.substring(0, sourceFilename.lastIndexOf(".")); 670 final String outputTitle = sourceTitle + "_golden_" + densityDpi; 671 final String outputFilename = outputFolder + outputTitle + ".png"; 672 final File outputFile = new File(outputFilename); 673 if (!outputFile.exists()) { 674 outputFile.createNewFile(); 675 } 676 677 out = new FileOutputStream(outputFile, false); 678 bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); 679 } catch (Exception e) { 680 e.printStackTrace(); 681 } finally { 682 if (out != null) { 683 try { 684 out.close(); 685 } catch (IOException e) { 686 e.printStackTrace(); 687 } 688 } 689 } 690 } 691 692 private class MockColorFilter extends ColorFilter { 693 } 694 } 695