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