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