1 /*
2  * Copyright (C) 2006 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;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.junit.Assert.assertEquals;
22 import static org.junit.Assert.assertTrue;
23 import static org.junit.Assert.fail;
24 
25 import android.app.IUriGrantsManager;
26 import android.content.ContentProvider;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.pm.ParceledListSlice;
30 import android.graphics.Bitmap;
31 import android.graphics.Canvas;
32 import android.graphics.RecordingCanvas;
33 import android.graphics.Region;
34 import android.net.Uri;
35 import android.os.Handler;
36 import android.os.HandlerThread;
37 import android.os.IBinder;
38 import android.os.Parcel;
39 import android.os.RemoteException;
40 import android.util.Log;
41 
42 import androidx.test.core.app.ApplicationProvider;
43 
44 import com.android.frameworks.coretests.R;
45 
46 import com.google.testing.junit.testparameterinjector.TestParameter;
47 import com.google.testing.junit.testparameterinjector.TestParameterInjector;
48 
49 import java.io.ByteArrayOutputStream;
50 import java.io.File;
51 import java.io.FileNotFoundException;
52 import java.io.FileOutputStream;
53 import java.util.ArrayList;
54 import java.util.Arrays;
55 
56 import org.junit.Before;
57 import org.junit.Test;
58 import org.junit.runner.RunWith;
59 
60 @RunWith(TestParameterInjector.class)
61 public class IconTest {
62     public static final String TAG = IconTest.class.getSimpleName();
63     private Context mContext;
64 
L(String s, Object... parts)65     public static void L(String s, Object... parts) {
66         Log.d(TAG, (parts.length == 0) ? s : String.format(s, parts));
67     }
68 
getContext()69     private Context getContext() {
70         return mContext;
71     }
72 
73     @Before
setup()74     public void setup() {
75         mContext = ApplicationProvider.getApplicationContext();
76     }
77 
78     @Test
testWithBitmap()79     public void testWithBitmap() throws Exception {
80         final Bitmap bm1 = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);
81         final Bitmap bm2 = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565);
82         final Bitmap bm3 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
83                 .getBitmap();
84 
85         final Canvas can1 = new Canvas(bm1);
86         can1.drawColor(0xFFFF0000);
87         final Canvas can2 = new Canvas(bm2);
88         can2.drawColor(0xFF00FF00);
89 
90         final Icon im1 = Icon.createWithBitmap(bm1);
91         final Icon im2 = Icon.createWithBitmap(bm2);
92         final Icon im3 = Icon.createWithBitmap(bm3);
93 
94         final Drawable draw1 = im1.loadDrawable(mContext);
95         final Drawable draw2 = im2.loadDrawable(mContext);
96         final Drawable draw3 = im3.loadDrawable(mContext);
97 
98         final Bitmap test1 = Bitmap.createBitmap(draw1.getIntrinsicWidth(),
99                 draw1.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
100         final Bitmap test2 = Bitmap.createBitmap(draw2.getIntrinsicWidth(),
101                 draw2.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
102         final Bitmap test3 = Bitmap.createBitmap(draw3.getIntrinsicWidth(),
103                 draw3.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
104 
105         draw1.setBounds(0, 0, draw1.getIntrinsicWidth(), draw1.getIntrinsicHeight());
106         draw1.draw(new Canvas(test1));
107 
108         draw2.setBounds(0, 0, draw2.getIntrinsicWidth(), draw2.getIntrinsicHeight());
109         draw2.draw(new Canvas(test2));
110 
111         draw3.setBounds(0, 0, draw3.getIntrinsicWidth(), draw3.getIntrinsicHeight());
112         draw3.draw(new Canvas(test3));
113 
114         final File dir = getContext().getExternalFilesDir(null);
115         L("writing temp bitmaps to %s...", dir);
116 
117         bm1.compress(Bitmap.CompressFormat.PNG, 100,
118                 new FileOutputStream(new File(dir, "bitmap1-original.png")));
119         test1.compress(Bitmap.CompressFormat.PNG, 100,
120                 new FileOutputStream(new File(dir, "bitmap1-test.png")));
121         if (!equalBitmaps(bm1, test1)) {
122             findBitmapDifferences(bm1, test1);
123             fail("bitmap1 differs, check " + dir);
124         }
125 
126         bm2.compress(Bitmap.CompressFormat.PNG, 100,
127                 new FileOutputStream(new File(dir, "bitmap2-original.png")));
128         test2.compress(Bitmap.CompressFormat.PNG, 100,
129                 new FileOutputStream(new File(dir, "bitmap2-test.png")));
130         if (!equalBitmaps(bm2, test2)) {
131             findBitmapDifferences(bm2, test2);
132             fail("bitmap2 differs, check " + dir);
133         }
134 
135         bm3.compress(Bitmap.CompressFormat.PNG, 100,
136                 new FileOutputStream(new File(dir, "bitmap3-original.png")));
137         test3.compress(Bitmap.CompressFormat.PNG, 100,
138                 new FileOutputStream(new File(dir, "bitmap3-test.png")));
139         if (!equalBitmaps(bm3, test3)) {
140             findBitmapDifferences(bm3, test3);
141             fail("bitmap3 differs, check " + dir);
142         }
143     }
144 
145     @Test
testScaleDownIfNecessary()146     public void testScaleDownIfNecessary() throws Exception {
147         final Bitmap bm = Bitmap.createBitmap(4321, 78, Bitmap.Config.ARGB_8888);
148         final Icon ic = Icon.createWithBitmap(bm);
149         ic.scaleDownIfNecessary(40, 20);
150 
151         assertThat(bm.getWidth()).isEqualTo(4321);
152         assertThat(bm.getHeight()).isEqualTo(78);
153 
154         assertThat(ic.getBitmap().getWidth()).isLessThan(41);
155         assertThat(ic.getBitmap().getHeight()).isLessThan(21);
156     }
157 
158     @Test
testWithAdaptiveBitmap()159     public void testWithAdaptiveBitmap() throws Exception {
160         final Bitmap bm1 = Bitmap.createBitmap(150, 150, Bitmap.Config.ARGB_8888);
161 
162         final Canvas can1 = new Canvas(bm1);
163         can1.drawColor(0xFFFF0000);
164 
165         final Icon im1 = Icon.createWithAdaptiveBitmap(bm1);
166 
167         final AdaptiveIconDrawable draw1 = (AdaptiveIconDrawable) im1.loadDrawable(mContext);
168 
169         final Bitmap test1 = Bitmap.createBitmap(
170             (int)(draw1.getIntrinsicWidth() * (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction())),
171             (int)(draw1.getIntrinsicHeight() * (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction())),
172             Bitmap.Config.ARGB_8888);
173 
174         draw1.setBounds(0, 0,
175             (int) (draw1.getIntrinsicWidth() * (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction())),
176             (int) (draw1.getIntrinsicHeight() * (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction())));
177         draw1.draw(new Canvas(test1));
178 
179         final File dir = getContext().getExternalFilesDir(null);
180         L("writing temp bitmaps to %s...", dir);
181 
182         bm1.compress(Bitmap.CompressFormat.PNG, 100,
183             new FileOutputStream(new File(dir, "adaptive-bitmap1-original.png")));
184         test1.compress(Bitmap.CompressFormat.PNG, 100,
185             new FileOutputStream(new File(dir, "adaptive-bitmap1-test.png")));
186         if (!equalBitmaps(bm1, test1, draw1.getSafeZone())) {
187             findBitmapDifferences(bm1, test1);
188             fail("adaptive bitmap1 differs, check " + dir);
189         }
190     }
191 
192     @Test
testWithBitmapResource()193     public void testWithBitmapResource() throws Exception {
194         final Bitmap res1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
195                 .getBitmap();
196 
197         final Icon im1 = Icon.createWithResource(getContext(), R.drawable.landscape);
198         final Drawable draw1 = im1.loadDrawable(mContext);
199         final Bitmap test1 = Bitmap.createBitmap(draw1.getIntrinsicWidth(),
200                 draw1.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
201         draw1.setBounds(0, 0, test1.getWidth(), test1.getHeight());
202         draw1.draw(new Canvas(test1));
203 
204         final File dir = getContext().getExternalFilesDir(null);
205         res1.compress(Bitmap.CompressFormat.PNG, 100,
206                 new FileOutputStream(new File(dir, "res1-original.png")));
207         test1.compress(Bitmap.CompressFormat.PNG, 100,
208                 new FileOutputStream(new File(dir, "res1-test.png")));
209         if (!equalBitmaps(res1, test1)) {
210             findBitmapDifferences(res1, test1);
211             fail("res1 differs, check " + dir);
212         }
213     }
214 
215     /**
216      * Icon resource test that ensures we can load and draw non-bitmaps. (In this case,
217      * stat_sys_adb is assumed, and asserted, to be a vector drawable.)
218      */
219     @Test
testWithStatSysAdbResource()220     public void testWithStatSysAdbResource() throws Exception {
221         // establish reference bitmap
222         final float dp = getContext().getResources().getDisplayMetrics().density;
223         final int stat_sys_adb_width = (int) (24 * dp);
224         final int stat_sys_adb_height = (int) (24 * dp);
225 
226         final Drawable stat_sys_adb = getContext()
227                 .getDrawable(com.android.internal.R.drawable.stat_sys_adb);
228         if (!(stat_sys_adb instanceof VectorDrawable)) {
229             fail("stat_sys_adb is a " + stat_sys_adb.toString()
230                     + ", not a VectorDrawable; stat_sys_adb malformed");
231         }
232 
233         if (stat_sys_adb.getIntrinsicWidth() != stat_sys_adb_width) {
234             fail("intrinsic width of stat_sys_adb is not 24dp; stat_sys_adb malformed");
235         }
236         if (stat_sys_adb.getIntrinsicHeight() != stat_sys_adb_height) {
237             fail("intrinsic height of stat_sys_adb is not 24dp; stat_sys_adb malformed");
238         }
239         final Bitmap referenceBitmap = Bitmap.createBitmap(
240                 stat_sys_adb_width,
241                 stat_sys_adb_height,
242                 Bitmap.Config.ARGB_8888);
243         stat_sys_adb.setBounds(0, 0, stat_sys_adb_width, stat_sys_adb_height);
244         stat_sys_adb.draw(new Canvas(referenceBitmap));
245 
246         final Icon im1 = Icon.createWithResource(getContext(),
247                 com.android.internal.R.drawable.stat_sys_adb);
248         final Drawable draw1 = im1.loadDrawable(getContext());
249 
250         assertEquals(stat_sys_adb.getIntrinsicWidth(), draw1.getIntrinsicWidth());
251         assertEquals(stat_sys_adb.getIntrinsicHeight(), draw1.getIntrinsicHeight());
252         assertEquals(im1.getResId(), com.android.internal.R.drawable.stat_sys_adb);
253 
254         final Bitmap test1 = Bitmap.createBitmap(
255                 draw1.getIntrinsicWidth(),
256                 draw1.getIntrinsicHeight(),
257                 Bitmap.Config.ARGB_8888);
258         draw1.setBounds(0, 0, test1.getWidth(), test1.getHeight());
259         draw1.draw(new Canvas(test1));
260 
261         final File dir = getContext().getExternalFilesDir(null);
262         test1.compress(Bitmap.CompressFormat.PNG, 100,
263                 new FileOutputStream(new File(dir, "testWithVectorDrawableResource-test.png")));
264         if (!equalBitmaps(referenceBitmap, test1)) {
265             findBitmapDifferences(referenceBitmap, test1);
266             fail("testWithFile: file1 differs, check " + dir);
267         }
268     }
269 
270     @Test
testWithFile()271     public void testWithFile() throws Exception {
272         final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
273                 .getBitmap();
274         final File dir = getContext().getExternalFilesDir(null);
275         final File file1 = new File(dir, "file1-original.png");
276         bit1.compress(Bitmap.CompressFormat.PNG, 100,
277                 new FileOutputStream(file1));
278 
279         final Icon im1 = Icon.createWithFilePath(file1.toString());
280         final Drawable draw1 = im1.loadDrawable(mContext);
281         final Bitmap test1 = Bitmap.createBitmap(draw1.getIntrinsicWidth(),
282                 draw1.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
283         draw1.setBounds(0, 0, test1.getWidth(), test1.getHeight());
284         draw1.draw(new Canvas(test1));
285 
286         test1.compress(Bitmap.CompressFormat.PNG, 100,
287                 new FileOutputStream(new File(dir, "file1-test.png")));
288         if (!equalBitmaps(bit1, test1)) {
289             findBitmapDifferences(bit1, test1);
290             fail("testWithFile: file1 differs, check " + dir);
291         }
292     }
293 
294     @Test
testWithAdaptiveIconResource_useMonochrome()295     public void testWithAdaptiveIconResource_useMonochrome() throws Exception {
296         final int colorMono = ((ColorDrawable) getContext().getDrawable(
297                 android.R.color.system_accent2_800)).getColor();
298         final Icon im1 = Icon.createWithResourceAdaptiveDrawable(getContext().getPackageName(),
299                 R.drawable.adaptiveicon_drawable, true, 0.0f);
300         final Drawable draw1 = im1.loadDrawable(mContext);
301         assertThat(draw1 instanceof InsetDrawable).isTrue();
302         ColorDrawable colorDrawable = (ColorDrawable) ((DrawableWrapper) draw1).getDrawable();
303         assertThat(colorDrawable.getColor()).isEqualTo(colorMono);
304     }
305 
306     @Test
testWithAdaptiveIconResource_dontUseMonochrome()307     public void testWithAdaptiveIconResource_dontUseMonochrome() throws Exception {
308         final int colorMono = ((ColorDrawable) getContext().getDrawable(
309                 android.R.color.system_accent2_800)).getColor();
310         final int colorFg = ((ColorDrawable) getContext().getDrawable(
311                 android.R.color.black)).getColor();
312         final int colorBg = ((ColorDrawable) getContext().getDrawable(
313                 android.R.color.white)).getColor();
314 
315         final Icon im1 = Icon.createWithResourceAdaptiveDrawable(getContext().getPackageName(),
316                 R.drawable.adaptiveicon_drawable, false , 0.0f);
317         final Drawable draw1 = im1.loadDrawable(mContext);
318         assertThat(draw1 instanceof AdaptiveIconDrawable).isTrue();
319         ColorDrawable colorDrawableMono = (ColorDrawable) ((AdaptiveIconDrawable) draw1)
320                 .getMonochrome();
321         assertThat(colorDrawableMono.getColor()).isEqualTo(colorMono);
322         ColorDrawable colorDrawableFg = (ColorDrawable) ((AdaptiveIconDrawable) draw1)
323                 .getForeground();
324         assertThat(colorDrawableFg.getColor()).isEqualTo(colorFg);
325         ColorDrawable colorDrawableBg = (ColorDrawable) ((AdaptiveIconDrawable) draw1)
326                 .getBackground();
327         assertThat(colorDrawableBg.getColor()).isEqualTo(colorBg);
328     }
329 
330     @Test
testAdaptiveIconResource_sameAs(@estParameter boolean useMonochrome)331     public void testAdaptiveIconResource_sameAs(@TestParameter boolean useMonochrome)
332             throws Exception {
333         final Icon im1 = Icon.createWithResourceAdaptiveDrawable(getContext().getPackageName(),
334                 R.drawable.adaptiveicon_drawable, useMonochrome, 1.0f);
335         final Parcel parcel = Parcel.obtain();
336         im1.writeToParcel(parcel, 0);
337         parcel.setDataPosition(0);
338         final Icon im2 = Icon.CREATOR.createFromParcel(parcel);
339         assertThat(im1.sameAs(im2)).isTrue();
340     }
341 
342     @Test
testAsync()343     public void testAsync() throws Exception {
344         final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
345                 .getBitmap();
346         final File dir = getContext().getExternalFilesDir(null);
347         final File file1 = new File(dir, "async-original.png");
348         bit1.compress(Bitmap.CompressFormat.PNG, 100,
349                 new FileOutputStream(file1));
350 
351         final Icon im1 = Icon.createWithFilePath(file1.toString());
352         final HandlerThread thd = new HandlerThread("testAsync");
353         thd.start();
354         final Handler h = new Handler(thd.getLooper());
355         L(TAG, "asyncTest: dispatching load to thread: " + thd);
356         im1.loadDrawableAsync(mContext, new Icon.OnDrawableLoadedListener() {
357             @Override
358             public void onDrawableLoaded(Drawable draw1) {
359                 L(TAG, "asyncTest: thread: loading drawable");
360                 L(TAG, "asyncTest: thread: loaded: %dx%d", draw1.getIntrinsicWidth(),
361                     draw1.getIntrinsicHeight());
362                 final Bitmap test1 = Bitmap.createBitmap(draw1.getIntrinsicWidth(),
363                         draw1.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
364                 draw1.setBounds(0, 0, test1.getWidth(), test1.getHeight());
365                 draw1.draw(new Canvas(test1));
366 
367                 try {
368                     test1.compress(Bitmap.CompressFormat.PNG, 100,
369                             new FileOutputStream(new File(dir, "async-test.png")));
370                 } catch (java.io.FileNotFoundException ex) {
371                     fail("couldn't create test file: " + ex);
372                 }
373                 if (!equalBitmaps(bit1, test1)) {
374                     findBitmapDifferences(bit1, test1);
375                     fail("testAsync: file1 differs, check " + dir);
376                 }
377             }
378         }, h);
379         L(TAG, "asyncTest: awaiting result");
380         Thread.sleep(500); // ;_;
381         assertTrue("async-test.png does not exist!", new File(dir, "async-test.png").exists());
382         L(TAG, "asyncTest: done");
383     }
384 
385     @Test
testParcel()386     public void testParcel() throws Exception {
387         final Bitmap originalbits = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
388                 .getBitmap();
389 
390         final ByteArrayOutputStream ostream = new ByteArrayOutputStream(
391                 originalbits.getWidth() * originalbits.getHeight() * 2); // guess 50% compression
392         originalbits.compress(Bitmap.CompressFormat.PNG, 100, ostream);
393         final byte[] pngdata = ostream.toByteArray();
394 
395         L("starting testParcel; bitmap: %d bytes, PNG: %d bytes",
396                 originalbits.getByteCount(),
397                 pngdata.length);
398 
399         final File dir = getContext().getExternalFilesDir(null);
400         final File originalfile = new File(dir, "parcel-original.png");
401         new FileOutputStream(originalfile).write(pngdata);
402 
403         ArrayList<Icon> imgs = new ArrayList<>();
404         final Icon file1 = Icon.createWithFilePath(originalfile.getAbsolutePath());
405         imgs.add(file1);
406         final Icon bit1 = Icon.createWithBitmap(originalbits);
407         imgs.add(bit1);
408         final Icon data1 = Icon.createWithData(pngdata, 0, pngdata.length);
409         imgs.add(data1);
410         final Icon res1 = Icon.createWithResource(getContext(), R.drawable.landscape);
411         imgs.add(res1);
412 
413         ArrayList<Icon> test = new ArrayList<>();
414         final Parcel parcel = Parcel.obtain();
415         int pos = 0;
416         parcel.writeInt(imgs.size());
417         for (Icon img : imgs) {
418             img.writeToParcel(parcel, 0);
419             L("used %d bytes parceling: %s", parcel.dataPosition() - pos, img);
420             pos = parcel.dataPosition();
421         }
422 
423         parcel.setDataPosition(0); // rewind
424         final int N = parcel.readInt();
425         for (int i=0; i<N; i++) {
426             Icon img = Icon.CREATOR.createFromParcel(parcel);
427             L("test %d: read from parcel: %s", i, img);
428             final File testfile = new File(dir,
429                     String.format("parcel-test%02d.png", i));
430 
431             final Drawable draw1 = img.loadDrawable(mContext);
432             if (draw1 == null) {
433                 fail("null drawable from img: " + img);
434             }
435             final Bitmap test1 = Bitmap.createBitmap(draw1.getIntrinsicWidth(),
436                     draw1.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
437             draw1.setBounds(0, 0, test1.getWidth(), test1.getHeight());
438             draw1.draw(new Canvas(test1));
439 
440             try {
441                 test1.compress(Bitmap.CompressFormat.PNG, 100,
442                         new FileOutputStream(testfile));
443             } catch (java.io.FileNotFoundException ex) {
444                 fail("couldn't create test file " + testfile + ": " + ex);
445             }
446             if (!equalBitmaps(originalbits, test1)) {
447                 findBitmapDifferences(originalbits, test1);
448                 fail(testfile + " differs from original: " + originalfile);
449             }
450 
451         }
452     }
453 
getMaxWidth(int origWidth, int origHeight, int maxNumPixels)454     private int getMaxWidth(int origWidth, int origHeight, int maxNumPixels) {
455         float aspRatio = (float) origWidth / (float) origHeight;
456         int newHeight = (int) Math.sqrt(maxNumPixels / aspRatio);
457         return (int) (newHeight * aspRatio);
458     }
459 
getMaxHeight(int origWidth, int origHeight, int maxNumPixels)460     private int getMaxHeight(int origWidth, int origHeight, int maxNumPixels) {
461         float aspRatio = (float) origWidth / (float) origHeight;
462         return (int) Math.sqrt(maxNumPixels / aspRatio);
463     }
464 
465     @Test
testScaleDownMaxSizeWithBitmap()466     public void testScaleDownMaxSizeWithBitmap() throws Exception {
467         final int bmpWidth = 13_000;
468         final int bmpHeight = 10_000;
469         final int bmpBpp = 4;
470         final int maxNumPixels = RecordingCanvas.MAX_BITMAP_SIZE / bmpBpp;
471         final int maxWidth = getMaxWidth(bmpWidth, bmpHeight, maxNumPixels);
472         final int maxHeight = getMaxHeight(bmpWidth, bmpHeight, maxNumPixels);
473 
474         final Bitmap bm = Bitmap.createBitmap(bmpWidth, bmpHeight, Bitmap.Config.ARGB_8888);
475         final Icon ic = Icon.createWithBitmap(bm);
476         final Drawable drawable = ic.loadDrawable(mContext);
477 
478         assertThat(Math.abs(drawable.getIntrinsicWidth() - maxWidth)).isLessThan(2);
479         assertThat(Math.abs(drawable.getIntrinsicHeight() - maxHeight)).isLessThan(2);
480     }
481 
482     @Test
testScaleDownMaxSizeWithAdaptiveBitmap()483     public void testScaleDownMaxSizeWithAdaptiveBitmap() throws Exception {
484         final int bmpWidth = 20_000;
485         final int bmpHeight = 10_000;
486         final int bmpBpp = 4;
487         final int maxNumPixels = RecordingCanvas.MAX_BITMAP_SIZE / bmpBpp;
488         final int maxWidth = getMaxWidth(bmpWidth, bmpHeight, maxNumPixels);
489         final int maxHeight = getMaxHeight(bmpWidth, bmpHeight, maxNumPixels);
490 
491         final Bitmap bm = Bitmap.createBitmap(bmpWidth, bmpHeight, Bitmap.Config.ARGB_8888);
492         final Icon ic = Icon.createWithAdaptiveBitmap(bm);
493         final AdaptiveIconDrawable adaptiveDrawable = (AdaptiveIconDrawable) ic.loadDrawable(
494                 mContext);
495         final Drawable drawable = adaptiveDrawable.getForeground();
496 
497         assertThat(drawable.getIntrinsicWidth()).isEqualTo(maxWidth);
498         assertThat(drawable.getIntrinsicHeight()).isEqualTo(maxHeight);
499     }
500 
501     @Test
testScaleDownMaxSizeWithResource()502     public void testScaleDownMaxSizeWithResource() throws Exception {
503         final Icon ic = Icon.createWithResource(getContext(), R.drawable.test_too_big);
504         final BitmapDrawable drawable = (BitmapDrawable) ic.loadDrawable(mContext);
505 
506         assertThat(drawable.getBitmap().getByteCount()).isAtMost(RecordingCanvas.MAX_BITMAP_SIZE);
507     }
508 
509     @Test
testScaleDownMaxSizeWithFile()510     public void testScaleDownMaxSizeWithFile() throws Exception {
511         final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.test_too_big))
512                 .getBitmap();
513         final File dir = getContext().getExternalFilesDir(null);
514         final File file1 = new File(dir, "file1-too-big.png");
515         bit1.compress(Bitmap.CompressFormat.PNG, 100,
516                 new FileOutputStream(file1));
517 
518         final Icon ic = Icon.createWithFilePath(file1.toString());
519         final BitmapDrawable drawable = (BitmapDrawable) ic.loadDrawable(mContext);
520 
521         assertThat(drawable.getBitmap().getByteCount()).isAtMost(RecordingCanvas.MAX_BITMAP_SIZE);
522     }
523 
524     @Test
testScaleDownMaxSizeWithData()525     public void testScaleDownMaxSizeWithData() throws Exception {
526         final int bmpBpp = 4;
527         final Bitmap originalBits = ((BitmapDrawable) getContext().getDrawable(
528                 R.drawable.test_too_big)).getBitmap();
529         final ByteArrayOutputStream ostream = new ByteArrayOutputStream(
530                 originalBits.getWidth() * originalBits.getHeight() * bmpBpp);
531         originalBits.compress(Bitmap.CompressFormat.PNG, 100, ostream);
532         final byte[] pngdata = ostream.toByteArray();
533         final Icon ic = Icon.createWithData(pngdata, 0, pngdata.length);
534         final BitmapDrawable drawable = (BitmapDrawable) ic.loadDrawable(mContext);
535 
536         assertThat(drawable.getBitmap().getByteCount()).isAtMost(RecordingCanvas.MAX_BITMAP_SIZE);
537     }
538 
539     @Test
testLoadSafeDrawable_loadSuccessful()540     public void testLoadSafeDrawable_loadSuccessful() throws FileNotFoundException {
541         int uid = 12345;
542         String packageName = "test_pkg";
543 
544         final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
545                 .getBitmap();
546         final File dir = getContext().getExternalFilesDir(null);
547         final File file1 = new File(dir, "file1-original.png");
548         bit1.compress(Bitmap.CompressFormat.PNG, 100, new FileOutputStream(file1));
549 
550         final Icon im1 = Icon.createWithFilePath(file1.toString());
551 
552         TestableIUriGrantsManager ugm =
553                 new TestableIUriGrantsManager(/* rejectCheckRequests */ false);
554 
555         Drawable loadedDrawable = im1.loadDrawableCheckingUriGrant(
556                 getContext(), ugm, uid, packageName);
557         assertThat(loadedDrawable).isNotNull();
558 
559         assertThat(ugm.mRequests.size()).isEqualTo(1);
560         TestableIUriGrantsManager.CheckRequest r = ugm.mRequests.get(0);
561         assertThat(r.mCallingUid).isEqualTo(uid);
562         assertThat(r.mPackageName).isEqualTo(packageName);
563         assertThat(r.mMode).isEqualTo(Intent.FLAG_GRANT_READ_URI_PERMISSION);
564         assertThat(r.mUri).isEqualTo(ContentProvider.getUriWithoutUserId(im1.getUri()));
565         assertThat(r.mUserId).isEqualTo(ContentProvider.getUserIdFromUri(im1.getUri()));
566 
567         final Bitmap test1 = Bitmap.createBitmap(loadedDrawable.getIntrinsicWidth(),
568                 loadedDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
569         loadedDrawable.setBounds(0, 0, loadedDrawable.getIntrinsicWidth(),
570                 loadedDrawable.getIntrinsicHeight());
571         loadedDrawable.draw(new Canvas(test1));
572 
573         bit1.compress(Bitmap.CompressFormat.PNG, 100,
574                 new FileOutputStream(new File(dir, "bitmap1-original.png")));
575         test1.compress(Bitmap.CompressFormat.PNG, 100,
576                 new FileOutputStream(new File(dir, "bitmap1-test.png")));
577         if (!equalBitmaps(bit1, test1)) {
578             findBitmapDifferences(bit1, test1);
579             fail("bitmap1 differs, check " + dir);
580         }
581     }
582 
583     @Test
testLoadSafeDrawable_grantRejected_nullDrawable()584     public void testLoadSafeDrawable_grantRejected_nullDrawable() throws FileNotFoundException {
585         int uid = 12345;
586         String packageName = "test_pkg";
587 
588         final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
589                 .getBitmap();
590         final File dir = getContext().getExternalFilesDir(null);
591         final File file1 = new File(dir, "file1-original.png");
592         bit1.compress(Bitmap.CompressFormat.PNG, 100, new FileOutputStream(file1));
593 
594         final Icon im1 = Icon.createWithFilePath(file1.toString());
595 
596         TestableIUriGrantsManager ugm =
597                 new TestableIUriGrantsManager(/* rejectCheckRequests */ true);
598 
599         Drawable loadedDrawable = im1.loadDrawableCheckingUriGrant(
600                 getContext(), ugm, uid, packageName);
601 
602         assertThat(ugm.mRequests.size()).isEqualTo(1);
603         TestableIUriGrantsManager.CheckRequest r = ugm.mRequests.get(0);
604         assertThat(r.mCallingUid).isEqualTo(uid);
605         assertThat(r.mPackageName).isEqualTo(packageName);
606         assertThat(r.mMode).isEqualTo(Intent.FLAG_GRANT_READ_URI_PERMISSION);
607         assertThat(r.mUri).isEqualTo(ContentProvider.getUriWithoutUserId(im1.getUri()));
608         assertThat(r.mUserId).isEqualTo(ContentProvider.getUserIdFromUri(im1.getUri()));
609 
610         assertThat(loadedDrawable).isNull();
611     }
612 
613 
614     // ======== utils ========
615 
616     static final char[] GRADIENT = " .:;+=xX$#".toCharArray();
617     static float[] hsv = new float[3];
colorToChar(int color)618     static char colorToChar(int color) {
619         int sum = ((color >> 16) & 0xff)
620                 + ((color >> 8)  & 0xff)
621                 + ((color)       & 0xff);
622         return GRADIENT[sum * (GRADIENT.length-1) / (3*0xff)];
623     }
printBits(int[] a, int w, int h)624     static void printBits(int[] a, int w, int h) {
625         final StringBuilder sb = new StringBuilder();
626         for (int i=0; i<w; i++) {
627             for (int j=0; j<h; j++) {
628                 sb.append(colorToChar(a[i+w*j]));
629             }
630             sb.append('\n');
631         }
632         L(sb.toString());
633     }
printBits(Bitmap a)634     static void printBits(Bitmap a) {
635         final int w = a.getWidth();
636         final int h = a.getHeight();
637         int[] aPix = new int[w * h];
638         printBits(aPix, w, h);
639     }
equalBitmaps(Bitmap a, Bitmap b)640     boolean equalBitmaps(Bitmap a, Bitmap b) {
641         return equalBitmaps(a, b, null);
642     }
643 
equalBitmaps(Bitmap a, Bitmap b, Region region)644     boolean equalBitmaps(Bitmap a, Bitmap b, Region region) {
645         if (a.getWidth() != b.getWidth() || a.getHeight() != b.getHeight()) return false;
646 
647         final int w = a.getWidth();
648         final int h = a.getHeight();
649         int[] aPix = new int[w * h];
650         int[] bPix = new int[w * h];
651 
652         if (region != null) {
653             for (int i = 0; i < w; i++) {
654                 for (int j = 0; j < h; j++) {
655                     if (region.contains(i, j) && a.getPixel(i, j) != b.getPixel(i, j)) {
656                         return false;
657                     }
658                 }
659             }
660             return true;
661         } else {
662             a.getPixels(aPix, 0, w, 0, 0, w, h);
663             b.getPixels(bPix, 0, w, 0, 0, w, h);
664             return Arrays.equals(aPix, bPix);
665         }
666     }
667 
findBitmapDifferences(Bitmap a, Bitmap b)668     void findBitmapDifferences(Bitmap a, Bitmap b) {
669         if (a.getWidth() != b.getWidth() || a.getHeight() != b.getHeight()) {
670             L("different sizes: %dx%d vs %dx%d",
671                         a.getWidth(), a.getHeight(), b.getWidth(), b.getHeight());
672             return;
673         }
674 
675         final int w = a.getWidth();
676         final int h = a.getHeight();
677         int[] aPix = new int[w * h];
678         int[] bPix = new int[w * h];
679 
680         a.getPixels(aPix, 0, w, 0, 0, w, h);
681         b.getPixels(bPix, 0, w, 0, 0, w, h);
682 
683         L("bitmap a (%dx%d)", w, h);
684         printBits(aPix, w, h);
685         L("bitmap b (%dx%d)", w, h);
686         printBits(bPix, w, h);
687 
688         StringBuffer sb = new StringBuffer("Different pixels: ");
689         for (int i=0; i<w; i++) {
690             for (int j=0; j<h; j++) {
691                 if (aPix[i+w*j] != bPix[i+w*j]) {
692                     sb.append(" ").append(i).append(",").append(j);
693                 }
694             }
695         }
696         L(sb.toString());
697     }
698 
699     private static class TestableIUriGrantsManager extends IUriGrantsManager.Stub {
700 
701         final ArrayList<CheckRequest> mRequests = new ArrayList<>();
702         final boolean mRejectCheckRequests;
703 
TestableIUriGrantsManager(boolean rejectCheckRequests)704         TestableIUriGrantsManager(boolean rejectCheckRequests) {
705             this.mRejectCheckRequests = rejectCheckRequests;
706         }
707 
708         @Override
takePersistableUriPermission(Uri uri, int i, String s, int i1)709         public void takePersistableUriPermission(Uri uri, int i, String s, int i1)
710                 throws RemoteException {
711 
712         }
713 
714         @Override
releasePersistableUriPermission(Uri uri, int i, String s, int i1)715         public void releasePersistableUriPermission(Uri uri, int i, String s, int i1)
716                 throws RemoteException {
717 
718         }
719 
720         @Override
grantUriPermissionFromOwner(IBinder iBinder, int i, String s, Uri uri, int i1, int i2, int i3)721         public void grantUriPermissionFromOwner(IBinder iBinder, int i, String s, Uri uri, int i1,
722                 int i2, int i3) throws RemoteException {
723 
724         }
725 
726         @Override
getGrantedUriPermissions(String s, int i)727         public ParceledListSlice getGrantedUriPermissions(String s, int i) throws RemoteException {
728             return null;
729         }
730 
731         @Override
clearGrantedUriPermissions(String s, int i)732         public void clearGrantedUriPermissions(String s, int i) throws RemoteException {
733 
734         }
735 
736         @Override
getUriPermissions(String s, boolean b, boolean b1)737         public ParceledListSlice getUriPermissions(String s, boolean b, boolean b1)
738                 throws RemoteException {
739             return null;
740         }
741 
742         @Override
checkGrantUriPermission_ignoreNonSystem( int uid, String packageName, Uri uri, int mode, int userId)743         public int checkGrantUriPermission_ignoreNonSystem(
744                 int uid, String packageName, Uri uri, int mode, int userId)
745                 throws RemoteException {
746             CheckRequest r = new CheckRequest(uid, packageName, uri, mode, userId);
747             mRequests.add(r);
748             if (mRejectCheckRequests) {
749                 throw new SecurityException();
750             } else {
751                 return uid;
752             }
753         }
754 
755         static class CheckRequest {
756             final int mCallingUid;
757             final String mPackageName;
758             final Uri mUri;
759             final int mMode;
760             final int mUserId;
761 
CheckRequest(int callingUid, String packageName, Uri uri, int mode, int userId)762             CheckRequest(int callingUid, String packageName, Uri uri, int mode, int userId) {
763                 this.mCallingUid = callingUid;
764                 this.mPackageName = packageName;
765                 this.mUri = uri;
766                 this.mMode = mode;
767                 this.mUserId = userId;
768             }
769         }
770     }
771 }