1 package android.graphics.drawable.cts;
2 
3 import static org.junit.Assert.assertEquals;
4 import static org.junit.Assert.assertFalse;
5 import static org.junit.Assert.assertNotNull;
6 import static org.junit.Assert.assertTrue;
7 import static org.junit.Assert.fail;
8 
9 import android.content.Context;
10 import android.content.res.Resources;
11 import android.graphics.Bitmap;
12 import android.graphics.Bitmap.Config;
13 import android.graphics.Canvas;
14 import android.graphics.Color;
15 import android.graphics.ColorFilter;
16 import android.graphics.Path;
17 import android.graphics.Path.Direction;
18 import android.graphics.PixelFormat;
19 import android.graphics.Rect;
20 import android.graphics.Region;
21 import android.graphics.cts.R;
22 import android.graphics.drawable.AdaptiveIconDrawable;
23 import android.graphics.drawable.BitmapDrawable;
24 import android.graphics.drawable.ColorDrawable;
25 import android.graphics.drawable.Drawable;
26 import android.graphics.drawable.Drawable.ConstantState;
27 import android.graphics.drawable.StateListDrawable;
28 import android.support.test.InstrumentationRegistry;
29 import android.support.test.filters.SmallTest;
30 import android.support.test.runner.AndroidJUnit4;
31 import android.util.AttributeSet;
32 import android.util.Log;
33 import android.util.Xml;
34 import java.io.File;
35 import java.io.FileOutputStream;
36 import java.util.Arrays;
37 import org.junit.Test;
38 import org.junit.runner.RunWith;
39 import org.xmlpull.v1.XmlPullParser;
40 
41 @SmallTest
42 @RunWith(AndroidJUnit4.class)
43 public class AdaptiveIconDrawableTest {
44 
45     public static final String TAG = AdaptiveIconDrawableTest.class.getSimpleName();
L(String s, Object... parts)46     public static void L(String s, Object... parts) {
47         Log.d(TAG, (parts.length == 0) ? s : String.format(s, parts));
48     }
49 
50     @Test
testConstructor()51     public void testConstructor() {
52         new AdaptiveIconDrawable(null, null);
53     }
54 
55     @Test
testInflate()56     public void testInflate() throws Throwable {
57         AdaptiveIconDrawable dr = new AdaptiveIconDrawable(null, null);
58 
59         Resources r = InstrumentationRegistry.getTargetContext().getResources();
60         XmlPullParser parser = r.getXml(R.layout.framelayout_layout);
61         AttributeSet attrs = Xml.asAttributeSet(parser);
62 
63         // Should not throw inflate exception
64         dr.inflate(r, parser, attrs);
65     }
66 
67     @Test(expected=NullPointerException.class)
testInflateNull()68     public void testInflateNull() throws Throwable {
69         AdaptiveIconDrawable dr = new AdaptiveIconDrawable(null, null);
70         dr.inflate(null, null, null);
71     }
72 
73     @Test
testDraw()74     public void testDraw() {
75         Canvas c = new Canvas();
76         AdaptiveIconDrawable dr = new AdaptiveIconDrawable(null, null);
77         dr.draw(c);
78     }
79 
80     @Test(expected=NullPointerException.class)
testDrawNull()81     public void testDrawNull() {
82         AdaptiveIconDrawable iconDrawable = new AdaptiveIconDrawable(
83             new ColorDrawable(Color.RED), new ColorDrawable(Color.BLUE));
84         iconDrawable.setBounds(0, 0, 100, 100);
85         iconDrawable.draw(null);
86     }
87 
88     @Test
testInvalidateDrawable()89     public void testInvalidateDrawable() {
90         AdaptiveIconDrawable iconDrawable = new AdaptiveIconDrawable(
91             new ColorDrawable(Color.RED), new ColorDrawable(Color.BLUE));
92         iconDrawable.invalidateDrawable(
93             InstrumentationRegistry.getTargetContext().getDrawable(R.drawable.pass));
94     }
95 
96     @Test
testScheduleDrawable()97     public void testScheduleDrawable() {
98         AdaptiveIconDrawable iconDrawable = new AdaptiveIconDrawable(
99             new ColorDrawable(Color.RED), new ColorDrawable(Color.BLUE));
100         iconDrawable.scheduleDrawable(
101             InstrumentationRegistry.getTargetContext().getDrawable(R.drawable.pass),
102             () -> {}, 10);
103 
104         // input null as params
105         iconDrawable.scheduleDrawable(null, null, -1);
106         // expected, no Exception thrown out, test success
107     }
108 
109     @Test
testUnscheduleDrawable()110     public void testUnscheduleDrawable() {
111         AdaptiveIconDrawable iconDrawable = new AdaptiveIconDrawable(
112             new ColorDrawable(Color.RED), new ColorDrawable(Color.BLUE));
113         iconDrawable.unscheduleDrawable(
114             InstrumentationRegistry.getTargetContext().getDrawable(R.drawable.pass), () -> {});
115 
116         // input null as params
117         iconDrawable.unscheduleDrawable(null, null);
118         // expected, no Exception thrown out, test success
119     }
120 
121     @Test
testGetChangingConfigurations()122     public void testGetChangingConfigurations() {
123         AdaptiveIconDrawable iconDrawable = new AdaptiveIconDrawable(
124             new ColorDrawable(Color.RED), new ColorDrawable(Color.BLUE));
125         iconDrawable.setChangingConfigurations(11);
126         assertEquals(11, iconDrawable.getChangingConfigurations());
127 
128         iconDrawable.setChangingConfigurations(-21);
129         assertEquals(-21, iconDrawable.getChangingConfigurations());
130     }
131 
132     /**
133      * When setBound isn't called before draw method is called.
134      * Nothing is drawn.
135      */
136     @Test
testDrawWithoutSetBounds()137     public void testDrawWithoutSetBounds() throws Exception {
138         Drawable backgroundDrawable = new ColorDrawable(Color.BLUE);
139         Drawable foregroundDrawable = new ColorDrawable(Color.RED);
140         AdaptiveIconDrawable iconDrawable = new AdaptiveIconDrawable(backgroundDrawable, foregroundDrawable);
141         Context context = InstrumentationRegistry.getTargetContext();
142         File dir = context.getExternalFilesDir(null);
143         L("writing temp bitmaps to %s...", dir);
144 
145         final Bitmap bm_test = Bitmap.createBitmap(150, 150, Bitmap.Config.ARGB_8888);
146         final Bitmap bm_org = bm_test.copy(Config.ARGB_8888, false);
147         final Canvas can1 = new Canvas(bm_test);
148 
149         // Even when setBounds is not called, should not crash
150         iconDrawable.draw(can1);
151         // Draws nothing! Hence same as original.
152         if (!equalBitmaps(bm_test, bm_org)) {
153             findBitmapDifferences(bm_test, bm_org);
154             fail("bm differs, check " + dir);
155         }
156     }
157 
158     /**
159      * When setBound is called, translate accordingly.
160      */
161     @Test
testDrawSetBounds()162     public void testDrawSetBounds() throws Exception {
163         int dpi = 4 ;
164         int top = 18 * dpi;
165         int left = 18 * dpi;
166         int right = 90 * dpi;
167         int bottom = 90 * dpi;
168         int width = right - left;
169         int height = bottom - top;
170         Context context = InstrumentationRegistry.getTargetContext();
171         AdaptiveIconDrawable iconDrawable = (AdaptiveIconDrawable) context.getResources().getDrawable(android.R.drawable.sym_def_app_icon);
172         File dir = context.getExternalFilesDir(null);
173         L("writing temp bitmaps to %s...", dir);
174         final Bitmap bm_org = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
175         final Canvas can_org = new Canvas(bm_org);
176         iconDrawable.setBounds(0, 0, width, height);
177         iconDrawable.draw(can_org);
178 
179         // Tested bitmap is drawn from the adaptive icon drawable.
180         final Bitmap bm_test = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
181         final Canvas can_test = new Canvas(bm_test);
182 
183         iconDrawable.setBounds(left, top, right, bottom);
184         can_test.translate(-left, -top);
185         iconDrawable.draw(can_test);
186         can_test.translate(left, top);
187 
188 
189         bm_org.compress(Bitmap.CompressFormat.PNG, 100,
190             new FileOutputStream(new File(dir, "adaptive-bm-original.png")));
191         bm_test.compress(Bitmap.CompressFormat.PNG, 100,
192             new FileOutputStream(new File(dir, "adaptive-bm-test.png")));
193         Region region = new Region(new Rect(0, 0, width, height));
194 
195         Path circle = new Path();
196         circle.addCircle(width / 2, height / 2,  (right - left)/2 -10 /* room for anti-alias */, Direction.CW);
197 
198         region.setPath(circle, region);
199         if (!equalBitmaps(bm_test, bm_org, region)) {
200             findBitmapDifferences(bm_test, bm_org);
201             fail("bm differs, check " + dir);
202         }
203     }
204 
205     @Test
testSetVisible()206     public void testSetVisible() {
207         AdaptiveIconDrawable iconDrawable = new AdaptiveIconDrawable(
208             new ColorDrawable(Color.RED), new ColorDrawable(Color.BLUE));
209         assertFalse(iconDrawable.setVisible(true, true)); /* unchanged */
210         assertTrue(iconDrawable.setVisible(false, true)); /* changed */
211         assertFalse(iconDrawable.setVisible(false, true)); /* unchanged */
212     }
213 
214     @Test
testSetAlpha()215     public void testSetAlpha() {
216         AdaptiveIconDrawable iconDrawable = new AdaptiveIconDrawable(
217             new ColorDrawable(Color.RED), new ColorDrawable(Color.BLUE));
218         iconDrawable.setAlpha(1);
219         iconDrawable.setAlpha(-1);
220 
221         iconDrawable.setAlpha(0);
222         iconDrawable.setAlpha(Integer.MAX_VALUE);
223         iconDrawable.setAlpha(Integer.MIN_VALUE);
224     }
225 
226     @Test
testSetColorFilter()227     public void testSetColorFilter() {
228         AdaptiveIconDrawable iconDrawable = new AdaptiveIconDrawable(
229             new ColorDrawable(Color.RED), new ColorDrawable(Color.BLUE));
230         ColorFilter cf = new ColorFilter();
231         iconDrawable.setColorFilter(cf);
232 
233         // input null as param
234         iconDrawable.setColorFilter(null);
235         // expected, no Exception thrown out, test success
236     }
237 
238     @Test
testGetOpacity()239     public void testGetOpacity() {
240         AdaptiveIconDrawable iconDrawable = new AdaptiveIconDrawable(
241             new ColorDrawable(Color.RED), new ColorDrawable(Color.BLUE));
242         iconDrawable.setOpacity(PixelFormat.OPAQUE);
243         assertEquals(PixelFormat.OPAQUE, iconDrawable.getOpacity());
244 
245         iconDrawable.setOpacity(PixelFormat.TRANSPARENT);
246         assertEquals(PixelFormat.TRANSPARENT, iconDrawable.getOpacity());
247     }
248 
249     @Test
testIsStateful()250     public void testIsStateful() {
251         AdaptiveIconDrawable iconDrawable = new AdaptiveIconDrawable(
252             new ColorDrawable(Color.RED), new ColorDrawable(Color.BLUE));
253         assertFalse(iconDrawable.isStateful());
254 
255         iconDrawable = new AdaptiveIconDrawable(new StateListDrawable(), new ColorDrawable(Color.RED));
256         assertTrue(iconDrawable.isStateful());
257     }
258 
259 
260     @Test
testGetConstantState()261     public void testGetConstantState() {
262         AdaptiveIconDrawable iconDrawable = new AdaptiveIconDrawable(
263             new ColorDrawable(Color.RED), new ColorDrawable(Color.BLUE));
264         ConstantState constantState = iconDrawable.getConstantState();
265         assertNotNull(constantState);
266     }
267 
268     @Test
testMutate()269     public void testMutate() {
270         // Obtain the first instance, then mutate and modify a property held by
271         // constant state. If mutate() works correctly, the property should not
272         // be modified on the second or third instances.
273         Resources res = InstrumentationRegistry.getTargetContext().getResources();
274         AdaptiveIconDrawable first = (AdaptiveIconDrawable) res.getDrawable(R.drawable.adaptive_icon_drawable, null);
275         AdaptiveIconDrawable pre = (AdaptiveIconDrawable) res.getDrawable(R.drawable.adaptive_icon_drawable, null);
276 
277         first.mutate().setBounds(0, 0, 100, 100);
278 
279         assertEquals("Modified first loaded instance", 100, first.getBounds().width());
280         assertEquals("Did not modify pre-mutate() instance", 0, pre.getBounds().width());
281 
282         AdaptiveIconDrawable post = (AdaptiveIconDrawable) res.getDrawable(R.drawable.adaptive_icon_drawable, null);
283 
284         assertEquals("Did not modify post-mutate() instance", 0, post.getBounds().width());
285     }
286 
287     @Test
testGetForegroundBackground()288     public void testGetForegroundBackground() {
289         Context context = InstrumentationRegistry.getTargetContext();
290         AdaptiveIconDrawable iconDrawable = new AdaptiveIconDrawable(
291             new ColorDrawable(Color.RED), new ColorDrawable(Color.BLUE));
292         Drawable fgDrawable = iconDrawable.getForeground();
293         Drawable bgDrawable = iconDrawable.getBackground();
294         assertTrue("Foreground layer is color drawable.", fgDrawable instanceof ColorDrawable);
295         assertTrue("Backgroud layer is color drawable.", bgDrawable instanceof ColorDrawable);
296 
297         AdaptiveIconDrawable iconDrawableInflated =
298             (AdaptiveIconDrawable) context.getDrawable(R.drawable.adaptive_icon_drawable);
299         fgDrawable = iconDrawableInflated.getForeground();
300         bgDrawable = iconDrawableInflated.getBackground();
301         assertTrue("Foreground layer is color drawable.", fgDrawable instanceof BitmapDrawable);
302         assertTrue("Backgroud layer is color drawable.", bgDrawable instanceof BitmapDrawable);
303     }
304 
305     @Test
testGetIntrinsicWidth()306     public void testGetIntrinsicWidth() {
307         Context context = InstrumentationRegistry.getTargetContext();
308         AdaptiveIconDrawable iconDrawableInflated =
309             (AdaptiveIconDrawable) context.getDrawable(R.drawable.adaptive_icon_drawable);
310         int fgWidth = iconDrawableInflated.getForeground().getIntrinsicWidth();
311         int bgWidth = iconDrawableInflated.getBackground().getIntrinsicWidth();
312         float iconIntrinsicWidth = Math.max(fgWidth, bgWidth) / (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction());
313         assertEquals("Max intrinsic width of the layers should be icon's intrinsic width",
314             (int) iconIntrinsicWidth, iconDrawableInflated.getIntrinsicWidth());
315     }
316 
317     @Test
testGetIntrinsicHeight()318     public void testGetIntrinsicHeight() {
319         Context context = InstrumentationRegistry.getTargetContext();
320         AdaptiveIconDrawable iconDrawableInflated =
321             (AdaptiveIconDrawable) context.getDrawable(R.drawable.adaptive_icon_drawable);
322         int fgWidth = iconDrawableInflated.getForeground().getIntrinsicHeight();
323         int bgWidth = iconDrawableInflated.getBackground().getIntrinsicHeight();
324         float iconIntrinsicHeight = Math.max(fgWidth, bgWidth) / (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction());
325         assertEquals("Max intrinsic height of the layers should be icon's intrinsic height",
326             (int) iconIntrinsicHeight, iconDrawableInflated.getIntrinsicHeight());
327     }
328 
329     //
330     // Utils
331     //
332 
equalBitmaps(Bitmap a, Bitmap b)333     boolean equalBitmaps(Bitmap a, Bitmap b) {
334       return equalBitmaps(a, b, null);
335     }
336 
equalBitmaps(Bitmap a, Bitmap b, Region region)337     boolean equalBitmaps(Bitmap a, Bitmap b, Region region) {
338         if (a.getWidth() != b.getWidth() || a.getHeight() != b.getHeight()) return false;
339 
340         final int w = a.getWidth();
341         final int h = a.getHeight();
342         int[] aPix = new int[w * h];
343         int[] bPix = new int[w * h];
344 
345         if (region != null) {
346             for (int i = 0; i < w; i++) {
347                 for (int j = 0; j < h; j++) {
348                     int ra = (a.getPixel(i, j) >> 16) & 0xff;
349                     int ga = (a.getPixel(i, j) >> 8) & 0xff;
350                     int ba = a.getPixel(i, j) & 0xff;
351                     int rb = (b.getPixel(i, j) >> 16) & 0xff;
352                     int gb = (b.getPixel(i, j) >> 8) & 0xff;
353                     int bb = b.getPixel(i, j) & 0xff;
354                     if (region.contains(i, j) && a.getPixel(i, j) != b.getPixel(i, j) ) {
355                         return false;
356                     }
357                 }
358             }
359             return true;
360         } else {
361             a.getPixels(aPix, 0, w, 0, 0, w, h);
362             b.getPixels(bPix, 0, w, 0, 0, w, h);
363             return Arrays.equals(aPix, bPix);
364         }
365     }
366 
findBitmapDifferences(Bitmap a, Bitmap b)367     void findBitmapDifferences(Bitmap a, Bitmap b) {
368         if (a.getWidth() != b.getWidth() || a.getHeight() != b.getHeight()) {
369             L("different sizes: %dx%d vs %dx%d",
370                 a.getWidth(), a.getHeight(), b.getWidth(), b.getHeight());
371             return;
372         }
373 
374         final int w = a.getWidth();
375         final int h = a.getHeight();
376         int[] aPix = new int[w * h];
377         int[] bPix = new int[w * h];
378 
379         a.getPixels(aPix, 0, w, 0, 0, w, h);
380         b.getPixels(bPix, 0, w, 0, 0, w, h);
381 
382         L("bitmap a (%dx%d)", w, h);
383         printBits(aPix, w, h);
384         L("bitmap b (%dx%d)", w, h);
385         printBits(bPix, w, h);
386 
387         StringBuffer sb = new StringBuffer("Different pixels: ");
388         for (int i=0; i<w; i++) {
389             for (int j=0; j<h; j++) {
390                 if (aPix[i+w*j] != bPix[i+w*j]) {
391                     sb.append(" ").append(i).append(",").append(j).append("<")
392                         .append(aPix[i+w*j]).append(",").append(bPix[i+w*j]).append(">");
393                 }
394             }
395         }
396         L(sb.toString());
397     }
398 
printBits(int[] a, int w, int h)399     static void printBits(int[] a, int w, int h) {
400         final StringBuilder sb = new StringBuilder();
401         for (int i=0; i<w; i++) {
402             for (int j=0; j<h; j++) {
403                 sb.append(colorToChar(a[i+w*j]));
404             }
405             sb.append('\n');
406         }
407         L(sb.toString());
408     }
409 
colorToChar(int color)410     static char colorToChar(int color) {
411         int sum = ((color >> 16) & 0xff)
412             + ((color >> 8)  & 0xff)
413             + ((color)       & 0xff);
414         return GRADIENT[sum * (GRADIENT.length-1) / (3*0xff)];
415     }
416     static final char[] GRADIENT = " .:;+=xX$#".toCharArray();
417 }
418