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