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