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