1 /*
2  * Copyright (C) 2017 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 androidx.core.graphics.drawable;
18 
19 import static androidx.annotation.RestrictTo.Scope.LIBRARY;
20 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
21 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
22 
23 import android.app.ActivityManager;
24 import android.content.ContentResolver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.pm.ApplicationInfo;
28 import android.content.pm.PackageManager;
29 import android.content.res.ColorStateList;
30 import android.content.res.Resources;
31 import android.graphics.Bitmap;
32 import android.graphics.BitmapFactory;
33 import android.graphics.BitmapShader;
34 import android.graphics.Canvas;
35 import android.graphics.Color;
36 import android.graphics.Matrix;
37 import android.graphics.Paint;
38 import android.graphics.PorterDuff;
39 import android.graphics.Shader;
40 import android.graphics.drawable.AdaptiveIconDrawable;
41 import android.graphics.drawable.BitmapDrawable;
42 import android.graphics.drawable.Drawable;
43 import android.graphics.drawable.Icon;
44 import android.net.Uri;
45 import android.os.Build;
46 import android.os.Bundle;
47 import android.os.Parcelable;
48 import android.text.TextUtils;
49 import android.util.Log;
50 
51 import androidx.annotation.ColorInt;
52 import androidx.annotation.DrawableRes;
53 import androidx.annotation.IdRes;
54 import androidx.annotation.IntDef;
55 import androidx.annotation.RequiresApi;
56 import androidx.annotation.RestrictTo;
57 import androidx.annotation.VisibleForTesting;
58 import androidx.core.content.ContextCompat;
59 import androidx.core.content.res.ResourcesCompat;
60 import androidx.core.util.ObjectsCompat;
61 import androidx.core.util.Preconditions;
62 import androidx.versionedparcelable.CustomVersionedParcelable;
63 import androidx.versionedparcelable.NonParcelField;
64 import androidx.versionedparcelable.ParcelField;
65 import androidx.versionedparcelable.VersionedParcelize;
66 
67 import org.jspecify.annotations.NonNull;
68 import org.jspecify.annotations.Nullable;
69 
70 import java.io.ByteArrayOutputStream;
71 import java.io.File;
72 import java.io.FileInputStream;
73 import java.io.FileNotFoundException;
74 import java.io.InputStream;
75 import java.lang.annotation.Retention;
76 import java.lang.annotation.RetentionPolicy;
77 import java.lang.reflect.InvocationTargetException;
78 import java.nio.charset.Charset;
79 
80 /**
81  * Helper for accessing features in {@link android.graphics.drawable.Icon}.
82  */
83 @VersionedParcelize(allowSerialization = true, ignoreParcelables = true, isCustom = true,
84         jetifyAs = "android.support.v4.graphics.drawable.IconCompat")
85 public class IconCompat extends CustomVersionedParcelable {
86 
87     private static final String TAG = "IconCompat";
88 
89     /**
90      * Value returned when the type of an {@link Icon} cannot be determined.
91      */
92     public static final int TYPE_UNKNOWN  = -1;
93     /**
94      * An icon that was created using {@link #createWithBitmap(Bitmap)}.
95      */
96     public static final int TYPE_BITMAP   = Icon.TYPE_BITMAP;
97     /**
98      * An icon that was created using {@link #createWithResource}.
99      */
100     public static final int TYPE_RESOURCE = Icon.TYPE_RESOURCE;
101     /**
102      * An icon that was created using {@link #createWithData(byte[], int, int)}.
103      */
104     public static final int TYPE_DATA     = Icon.TYPE_DATA;
105     /**
106      * An icon that was created using {@link #createWithContentUri}.
107      */
108     public static final int TYPE_URI      = Icon.TYPE_URI;
109     /**
110      * An icon that was created using {@link #createWithAdaptiveBitmap}.
111      */
112     public static final int TYPE_ADAPTIVE_BITMAP = Icon.TYPE_ADAPTIVE_BITMAP;
113 
114     /**
115      * An icon that was created using {@link #createWithAdaptiveBitmapContentUri}.
116      */
117     public static final int TYPE_URI_ADAPTIVE_BITMAP = Icon.TYPE_URI_ADAPTIVE_BITMAP;
118 
119     /**
120      */
121     @RestrictTo(LIBRARY)
122     @IntDef({TYPE_UNKNOWN, TYPE_BITMAP, TYPE_RESOURCE, TYPE_DATA, TYPE_URI, TYPE_ADAPTIVE_BITMAP,
123             TYPE_URI_ADAPTIVE_BITMAP})
124     @Retention(RetentionPolicy.SOURCE)
125     public @interface IconType {
126     }
127 
128     // Ratio of expected size to actual icon size
129     private static final float ADAPTIVE_ICON_INSET_FACTOR = 1 / 4f;
130     private static final float DEFAULT_VIEW_PORT_SCALE = 1 / (1 + 2 * ADAPTIVE_ICON_INSET_FACTOR);
131     private static final float ICON_DIAMETER_FACTOR = 176f / 192;
132     private static final float BLUR_FACTOR = 0.5f / 48;
133     private static final float KEY_SHADOW_OFFSET_FACTOR = 1f / 48;
134 
135     private static final int KEY_SHADOW_ALPHA = 61;
136     private static final int AMBIENT_SHADOW_ALPHA = 30;
137 
138     @VisibleForTesting
139     static final String EXTRA_TYPE = "type";
140     @VisibleForTesting
141     static final String EXTRA_OBJ = "obj";
142     @VisibleForTesting
143     static final String EXTRA_INT1 = "int1";
144     @VisibleForTesting
145     static final String EXTRA_INT2 = "int2";
146     @VisibleForTesting
147     static final String EXTRA_TINT_LIST = "tint_list";
148     @VisibleForTesting
149     static final String EXTRA_TINT_MODE = "tint_mode";
150     @VisibleForTesting
151     static final String EXTRA_STRING1 = "string1";
152 
153     /**
154      */
155     @RestrictTo(LIBRARY_GROUP_PREFIX)
156     @ParcelField(value = 1,
157             defaultValue = "androidx.core.graphics.drawable.IconCompat.TYPE_UNKNOWN")
158     public int mType = TYPE_UNKNOWN;
159 
160     // To avoid adding unnecessary overhead, we have a few basic objects that get repurposed
161     // based on the value of mType.
162 
163     // TYPE_BITMAP: Bitmap
164     // TYPE_ADAPTIVE_BITMAP: Bitmap
165     // TYPE_RESOURCE: String
166     // TYPE_URI: String
167     // TYPE_DATA: DataBytes
168     @NonParcelField
169     Object          mObj1;
170 
171     /**
172      */
173     @RestrictTo(LIBRARY)
174     @ParcelField(value = 2, defaultValue = "null")
175     public byte @Nullable []          mData = null;
176     /**
177      */
178     @RestrictTo(LIBRARY)
179     @ParcelField(value = 3, defaultValue = "null")
180     public @Nullable Parcelable      mParcelable = null;
181 
182     // TYPE_RESOURCE: resId
183     // TYPE_DATA: data offset
184     /**
185      */
186     @RestrictTo(LIBRARY)
187     @ParcelField(value = 4, defaultValue = "0")
188     public int             mInt1 = 0;
189 
190     // TYPE_DATA: data length
191     /**
192      */
193     @RestrictTo(LIBRARY)
194     @ParcelField(value = 5, defaultValue = "0")
195     public int             mInt2 = 0;
196 
197     /**
198      */
199     @RestrictTo(LIBRARY)
200     @ParcelField(value = 6, defaultValue = "null")
201     public @Nullable ColorStateList  mTintList = null;
202 
203     static final PorterDuff.Mode DEFAULT_TINT_MODE = PorterDuff.Mode.SRC_IN; // SRC_IN
204     @NonParcelField
205     PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE;
206     /**
207      */
208     @RestrictTo(LIBRARY)
209     @ParcelField(value = 7, defaultValue = "null")
210     public @Nullable String mTintModeStr = null;
211 
212     /**
213      */
214     @RestrictTo(LIBRARY)
215     @ParcelField(value = 8, defaultValue = "null")
216     public @Nullable String mString1;
217 
218     /**
219      * Create an Icon pointing to a drawable resource.
220      * @param context The context for the application whose resources should be used to resolve the
221      *                given resource ID.
222      * @param resId ID of the drawable resource
223      * @see android.graphics.drawable.Icon#createWithResource(Context, int)
224      */
createWithResource(@onNull Context context, @DrawableRes int resId)225     public static @NonNull IconCompat createWithResource(@NonNull Context context,
226             @DrawableRes int resId) {
227         ObjectsCompat.requireNonNull(context);
228         return createWithResource(context.getResources(), context.getPackageName(), resId);
229     }
230 
231     /**
232      */
233     @RestrictTo(LIBRARY_GROUP_PREFIX)
createWithResource(@ullable Resources r, @NonNull String pkg, @DrawableRes int resId)234     public static @NonNull IconCompat createWithResource(@Nullable Resources r,
235             @NonNull String pkg,
236             @DrawableRes int resId) {
237         ObjectsCompat.requireNonNull(pkg);
238         if (resId == 0) {
239             throw new IllegalArgumentException("Drawable resource ID must not be 0");
240         }
241         final IconCompat rep = new IconCompat(TYPE_RESOURCE);
242         rep.mInt1 = resId;
243         if (r != null) {
244             try {
245                 rep.mObj1 = r.getResourceName(resId);
246             } catch (Resources.NotFoundException e) {
247                 throw new IllegalArgumentException("Icon resource cannot be found");
248             }
249         } else {
250             rep.mObj1 = pkg;
251         }
252         rep.mString1 = pkg;
253         return rep;
254     }
255 
256     /**
257      * Create an Icon pointing to a bitmap in memory.
258      * @param bits A valid {@link android.graphics.Bitmap} object
259      * @see android.graphics.drawable.Icon#createWithBitmap(Bitmap)
260      */
createWithBitmap(@onNull Bitmap bits)261     public static @NonNull IconCompat createWithBitmap(@NonNull Bitmap bits) {
262         ObjectsCompat.requireNonNull(bits);
263         final IconCompat rep = new IconCompat(TYPE_BITMAP);
264         rep.mObj1 = bits;
265         return rep;
266     }
267 
268     /**
269      * Create an Icon pointing to a bitmap in memory that follows the icon design guideline defined
270      * by {@link android.graphics.drawable.AdaptiveIconDrawable}.
271      * @param bits A valid {@link android.graphics.Bitmap} object
272      * @see android.graphics.drawable.Icon#createWithAdaptiveBitmap(Bitmap)
273      */
createWithAdaptiveBitmap(@onNull Bitmap bits)274     public static @NonNull IconCompat createWithAdaptiveBitmap(@NonNull Bitmap bits) {
275         ObjectsCompat.requireNonNull(bits);
276         final IconCompat rep = new IconCompat(TYPE_ADAPTIVE_BITMAP);
277         rep.mObj1 = bits;
278         return rep;
279     }
280 
281     /**
282      * Create an Icon pointing to a compressed bitmap stored in a byte array.
283      * @param data Byte array storing compressed bitmap data of a type that
284      *             {@link android.graphics.BitmapFactory}
285      *             can decode (see {@link android.graphics.Bitmap.CompressFormat}).
286      * @param offset Offset into <code>data</code> at which the bitmap data starts
287      * @param length Length of the bitmap data
288      * @see android.graphics.drawable.Icon#createWithData(byte[], int, int)
289      */
createWithData(byte @NonNull [] data, int offset, int length)290     public static @NonNull IconCompat createWithData(byte @NonNull [] data, int offset,
291             int length) {
292         ObjectsCompat.requireNonNull(data);
293         final IconCompat rep = new IconCompat(TYPE_DATA);
294         rep.mObj1 = data;
295         rep.mInt1 = offset;
296         rep.mInt2 = length;
297         return rep;
298     }
299 
300     /**
301      * Create an Icon pointing to an image file specified by URI.
302      *
303      * @param uri A uri referring to local content:// or file:// image data.
304      * @see android.graphics.drawable.Icon#createWithContentUri(String)
305      */
createWithContentUri(@onNull String uri)306     public static @NonNull IconCompat createWithContentUri(@NonNull String uri) {
307         ObjectsCompat.requireNonNull(uri);
308         final IconCompat rep = new IconCompat(TYPE_URI);
309         rep.mObj1 = uri;
310         return rep;
311     }
312 
313     /**
314      * Create an Icon pointing to an image file specified by URI.
315      *
316      * @param uri A uri referring to local content:// or file:// image data.
317      * @see android.graphics.drawable.Icon#createWithContentUri(String)
318      */
createWithContentUri(@onNull Uri uri)319     public static @NonNull IconCompat createWithContentUri(@NonNull Uri uri) {
320         ObjectsCompat.requireNonNull(uri);
321         return createWithContentUri(uri.toString());
322     }
323 
324     /**
325      * Create an Icon pointing to an image file specified by URI. Image file should follow the icon
326      * design guideline defined by {@link AdaptiveIconDrawable}.
327      *
328      * @param uri A uri referring to local content:// or file:// image data.
329      * @see android.graphics.drawable.Icon#createWithAdaptiveBitmapContentUri(String)
330      */
createWithAdaptiveBitmapContentUri(@onNull String uri)331     public static @NonNull IconCompat createWithAdaptiveBitmapContentUri(@NonNull String uri) {
332         ObjectsCompat.requireNonNull(uri);
333         final IconCompat rep = new IconCompat(TYPE_URI_ADAPTIVE_BITMAP);
334         rep.mObj1 = uri;
335         return rep;
336     }
337 
338     /**
339      * Create an Icon pointing to an image file specified by URI. Image file should follow the icon
340      * design guideline defined by {@link AdaptiveIconDrawable}.
341      *
342      * @param uri A uri referring to local content:// or file:// image data.
343      * @see android.graphics.drawable.Icon#createWithAdaptiveBitmapContentUri(String)
344      */
createWithAdaptiveBitmapContentUri(@onNull Uri uri)345     public static @NonNull IconCompat createWithAdaptiveBitmapContentUri(@NonNull Uri uri) {
346         ObjectsCompat.requireNonNull(uri);
347         return createWithAdaptiveBitmapContentUri(uri.toString());
348     }
349 
350     /**
351      * Used for VersionedParcelable.
352      */
353     @RestrictTo(LIBRARY)
IconCompat()354     public IconCompat() {
355     }
356 
IconCompat(int mType)357     IconCompat(int mType) {
358         this.mType = mType;
359     }
360 
361     /**
362      * Gets the type of the icon provided.
363      * <p>
364      * Note that new types may be added later, so callers should guard against other
365      * types being returned.
366      */
367     @IconType
getType()368     public int getType() {
369         if (mType == TYPE_UNKNOWN && Build.VERSION.SDK_INT >= 23) {
370             return Api23Impl.getType(mObj1);
371         }
372         return mType;
373     }
374 
375     /**
376      * Gets the package used to create this icon.
377      * <p>
378      * Only valid for icons of type TYPE_RESOURCE.
379      * Note: This package may not be available if referenced in the future, and it is
380      * up to the caller to ensure safety if this package is re-used and/or persisted.
381      */
getResPackage()382     public @NonNull String getResPackage() {
383         if (mType == TYPE_UNKNOWN && Build.VERSION.SDK_INT >= 23) {
384             return Api23Impl.getResPackage(mObj1);
385         }
386         if (mType != TYPE_RESOURCE) {
387             throw new IllegalStateException("called getResPackage() on " + this);
388         }
389         // Before aosp/1307777, we don't put the package name to mString1. Try to get the
390         // package name from the full resource name string. Note that this is not always the same
391         // as "the package used to create this icon" and this was what aosp/1307777 tried to fix.
392         if (mString1 == null || TextUtils.isEmpty(mString1)) {
393             return ((String) mObj1).split(":", -1)[0];
394         } else {
395             // The name of the getResPackage() API is a bit confusing. It actually returns
396             // the app package name rather than the package name in the resource table.
397             return mString1;
398         }
399     }
400 
401     /**
402      * Gets the drawable resource id used to create this icon.
403      * <p>
404      * Only valid for icons of type TYPE_RESOURCE.
405      * Note: This resource may not be available if the application changes at all, and it is
406      * up to the caller to ensure safety if this resource is re-used and/or persisted.
407      */
408     @DrawableRes
getResId()409     public int getResId() {
410         if (mType == TYPE_UNKNOWN && Build.VERSION.SDK_INT >= 23) {
411             return Api23Impl.getResId(mObj1);
412         }
413         if (mType != TYPE_RESOURCE) {
414             throw new IllegalStateException("called getResId() on " + this);
415         }
416         return mInt1;
417     }
418 
419     /**
420      * Gets the bitmap used to create this icon.
421      * <p>
422      * Only valid for icons of type TYPE_BITMAP.
423      * Note: This bitmap may not be available in the future, and it is
424      * up to the caller to ensure safety if this bitmap is re-used and/or persisted.
425      *
426      */
427     @RestrictTo(LIBRARY_GROUP_PREFIX)
getBitmap()428     public @Nullable Bitmap getBitmap() {
429         if (mType == TYPE_UNKNOWN && Build.VERSION.SDK_INT >= 23) {
430             if (mObj1 instanceof Bitmap) {
431                 return (Bitmap) mObj1;
432             }
433             return null;
434         }
435         if (mType == TYPE_BITMAP) {
436             return (Bitmap) mObj1;
437         } else if (mType == TYPE_ADAPTIVE_BITMAP) {
438             return createLegacyIconFromAdaptiveIcon((Bitmap) mObj1, true);
439         } else {
440             throw new IllegalStateException("called getBitmap() on " + this);
441         }
442     }
443 
444     /**
445      * Gets the uri used to create this icon.
446      * <p>
447      * Only valid for icons of type TYPE_URI.
448      * Note: This uri may not be available in the future, and it is
449      * up to the caller to ensure safety if this uri is re-used and/or persisted.
450      */
getUri()451     public @NonNull Uri getUri() {
452         if (mType == TYPE_UNKNOWN && Build.VERSION.SDK_INT >= 23) {
453             return Api23Impl.getUri(mObj1);
454         }
455         if (mType != TYPE_URI && mType != TYPE_URI_ADAPTIVE_BITMAP) {
456             throw new IllegalStateException("called getUri() on " + this);
457         }
458         return Uri.parse((String) mObj1);
459     }
460 
461     /**
462      * Store a color to use whenever this Icon is drawn.
463      *
464      * @param tint a color, as in {@link Drawable#setTint(int)}
465      * @return this same object, for use in chained construction
466      */
setTint(@olorInt int tint)467     public @NonNull IconCompat setTint(@ColorInt int tint) {
468         return setTintList(ColorStateList.valueOf(tint));
469     }
470 
471     /**
472      * Store a color to use whenever this Icon is drawn.
473      *
474      * @param tintList as in {@link Drawable#setTintList(ColorStateList)}, null to remove tint
475      * @return this same object, for use in chained construction
476      */
setTintList(@ullable ColorStateList tintList)477     public @NonNull IconCompat setTintList(@Nullable ColorStateList tintList) {
478         mTintList = tintList;
479         return this;
480     }
481 
482     /**
483      * Store a blending mode to use whenever this Icon is drawn.
484      *
485      * @param mode a blending mode, as in {@link Drawable#setTintMode(PorterDuff.Mode)}, may be null
486      * @return this same object, for use in chained construction
487      */
setTintMode(PorterDuff.@ullable Mode mode)488     public @NonNull IconCompat setTintMode(PorterDuff.@Nullable Mode mode) {
489         mTintMode = mode;
490         return this;
491     }
492 
493     /**
494      * @deprecated Use {@link #toIcon(Context)} to generate the {@link Icon} object.
495      */
496     @RequiresApi(23)
497     @Deprecated
toIcon()498     public @NonNull Icon toIcon() {
499         return toIcon(null);
500     }
501 
502     /**
503      * Convert this compat object to {@link Icon} object.
504      *
505      * @return {@link Icon} object
506      */
507     @RequiresApi(23)
toIcon(@ullable Context context)508     public @NonNull Icon toIcon(@Nullable Context context) {
509         if (Build.VERSION.SDK_INT >= 23) {
510             return Api23Impl.toIcon(this, context);
511         } else {
512             throw new UnsupportedOperationException(
513                     "This method is only supported on API level 23+");
514         }
515     }
516 
517     /**
518      */
519     @RestrictTo(LIBRARY_GROUP_PREFIX)
checkResource(@onNull Context context)520     public void checkResource(@NonNull Context context) {
521         if (mType == TYPE_RESOURCE && mObj1 != null) {
522             String fullResName = (String) mObj1;
523             if (!fullResName.contains(":")) {
524                 return;
525             }
526             // Do some splitting to parse out each of the components.
527             String resName = fullResName.split(":", -1)[1];
528             String resType = resName.split("/", -1)[0];
529             resName = resName.split("/", -1)[1];
530             String resPackage = fullResName.split(":", -1)[0];
531             if ("0_resource_name_obfuscated".equals(resName)) {
532                 // All obfuscated resources have the same name, so not going to look up the
533                 // resource identifier from the resource name.
534                 Log.i(TAG, "Found obfuscated resource, not trying to update resource id for it");
535                 return;
536             }
537             String appPackage = getResPackage();
538             Resources res = getResources(context, appPackage);
539             int id = res.getIdentifier(resName, resType, resPackage);
540             if (mInt1 != id) {
541                 Log.i(TAG, "Id has changed for " + appPackage + " " + fullResName);
542                 mInt1 = id;
543             }
544         }
545     }
546 
547     /**
548      * Returns a Drawable that can be used to draw the image inside this Icon, constructing it
549      * if necessary.
550      *
551      * @param context {@link android.content.Context Context} in which to load the drawable; used
552      *                to access {@link android.content.res.Resources Resources}, for example.
553      * @return A fresh instance of a drawable for this image, yours to keep.
554      */
loadDrawable(@onNull Context context)555     public @Nullable Drawable loadDrawable(@NonNull Context context) {
556         checkResource(context);
557         if (Build.VERSION.SDK_INT >= 23) {
558             return Api23Impl.loadDrawable(toIcon(context), context);
559         }
560         final Drawable result = loadDrawableInner(context);
561         if (result != null && (mTintList != null || mTintMode != DEFAULT_TINT_MODE)) {
562             result.mutate();
563             DrawableCompat.setTintList(result, mTintList);
564             DrawableCompat.setTintMode(result, mTintMode);
565         }
566         return result;
567     }
568 
569     /**
570      * Do the heavy lifting of loading the drawable, but stop short of applying any tint.
571      */
loadDrawableInner(Context context)572     private Drawable loadDrawableInner(Context context) {
573         switch (mType) {
574             case TYPE_BITMAP:
575                 return new BitmapDrawable(context.getResources(), (Bitmap) mObj1);
576             case TYPE_ADAPTIVE_BITMAP:
577                 return new BitmapDrawable(context.getResources(),
578                         createLegacyIconFromAdaptiveIcon((Bitmap) mObj1, false));
579             case TYPE_RESOURCE:
580                 // figure out where to load resources from
581                 String resPackage = getResPackage();
582                 if (TextUtils.isEmpty(resPackage)) {
583                     // if none is specified, try the given context
584                     resPackage = context.getPackageName();
585                 }
586                 Resources res = getResources(context, resPackage);
587                 try {
588                     return ResourcesCompat.getDrawable(res, mInt1, context.getTheme());
589                 } catch (RuntimeException e) {
590                     Log.e(TAG, String.format("Unable to load resource 0x%08x from pkg=%s",
591                             mInt1,
592                             mObj1),
593                             e);
594                 }
595                 break;
596             case TYPE_DATA:
597                 return new BitmapDrawable(context.getResources(),
598                         BitmapFactory.decodeByteArray((byte[]) mObj1, mInt1, mInt2)
599                 );
600             case TYPE_URI:
601                 InputStream is = getUriInputStream(context);
602                 if (is != null) {
603                     return new BitmapDrawable(context.getResources(),
604                             BitmapFactory.decodeStream(is));
605                 }
606                 break;
607             case TYPE_URI_ADAPTIVE_BITMAP:
608                 is = getUriInputStream(context);
609                 if (is != null) {
610                     if (Build.VERSION.SDK_INT >= 26) {
611                         return Api26Impl.createAdaptiveIconDrawable(null,
612                                 new BitmapDrawable(context.getResources(),
613                                         BitmapFactory.decodeStream(is)));
614                     } else {
615                         return new BitmapDrawable(context.getResources(),
616                                 createLegacyIconFromAdaptiveIcon(
617                                         BitmapFactory.decodeStream(is), false));
618                     }
619                 }
620                 break;
621         }
622         return null;
623     }
624 
625     /**
626      * Create an input stream for bitmap by resolving corresponding content uri.
627      *
628      */
629     @RestrictTo(LIBRARY_GROUP)
getUriInputStream(@onNull Context context)630     public @Nullable InputStream getUriInputStream(@NonNull Context context) {
631         final Uri uri = getUri();
632         final String scheme = uri.getScheme();
633         if (ContentResolver.SCHEME_CONTENT.equals(scheme)
634                 || ContentResolver.SCHEME_FILE.equals(scheme)) {
635             try {
636                 return context.getContentResolver().openInputStream(uri);
637             } catch (Exception e) {
638                 Log.w(TAG, "Unable to load image from URI: " + uri, e);
639             }
640         } else {
641             try {
642                 return new FileInputStream(new File((String) mObj1));
643             } catch (FileNotFoundException e) {
644                 Log.w(TAG, "Unable to load image from path: " + uri, e);
645             }
646         }
647         return null;
648     }
649 
650     @SuppressWarnings("deprecation")
getResources(Context context, String resPackage)651     static Resources getResources(Context context, String resPackage) {
652         if ("android".equals(resPackage)) {
653             return Resources.getSystem();
654         } else {
655             final PackageManager pm = context.getPackageManager();
656             try {
657                 ApplicationInfo ai = pm.getApplicationInfo(
658                         resPackage, PackageManager.MATCH_UNINSTALLED_PACKAGES);
659                 if (ai != null) {
660                     return pm.getResourcesForApplication(ai);
661                 } else {
662                     return null;
663                 }
664             } catch (PackageManager.NameNotFoundException e) {
665                 Log.e(TAG, String.format("Unable to find pkg=%s for icon",
666                         resPackage), e);
667                 return null;
668             }
669         }
670     }
671 
672 
673     /**
674      */
675     @RestrictTo(LIBRARY_GROUP_PREFIX)
676     @SuppressWarnings("deprecation")
addToShortcutIntent(@onNull Intent outIntent, @Nullable Drawable badge, @NonNull Context c)677     public void addToShortcutIntent(@NonNull Intent outIntent, @Nullable Drawable badge,
678             @NonNull Context c) {
679         checkResource(c);
680         Bitmap icon;
681         switch (mType) {
682             case TYPE_BITMAP:
683                 icon = (Bitmap) mObj1;
684                 if (badge != null) {
685                     // Do not modify the original icon when applying a badge
686                     icon = icon.copy(icon.getConfig(), true);
687                 }
688                 break;
689             case TYPE_ADAPTIVE_BITMAP:
690                 icon = createLegacyIconFromAdaptiveIcon((Bitmap) mObj1, true);
691                 break;
692             case TYPE_RESOURCE:
693                 try {
694                     Context context = c.createPackageContext(getResPackage(), 0);
695                     if (badge == null) {
696                         outIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
697                                 Intent.ShortcutIconResource.fromContext(context, mInt1));
698                         return;
699                     } else {
700                         Drawable dr = ContextCompat.getDrawable(context, mInt1);
701                         if (dr.getIntrinsicWidth() <= 0 || dr.getIntrinsicHeight() <= 0) {
702                             int size = ((ActivityManager) context.getSystemService(
703                                     Context.ACTIVITY_SERVICE)).getLauncherLargeIconSize();
704                             icon = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
705                         } else {
706                             icon = Bitmap.createBitmap(dr.getIntrinsicWidth(),
707                                     dr.getIntrinsicHeight(),
708                                     Bitmap.Config.ARGB_8888);
709                         }
710                         dr.setBounds(0, 0, icon.getWidth(), icon.getHeight());
711                         dr.draw(new Canvas(icon));
712                     }
713                 } catch (PackageManager.NameNotFoundException e) {
714                     throw new IllegalArgumentException("Can't find package " + mObj1, e);
715                 }
716                 break;
717             default:
718                 throw new IllegalArgumentException("Icon type not supported for intent shortcuts");
719         }
720         if (badge != null) {
721             // Badge the icon
722             int w = icon.getWidth();
723             int h = icon.getHeight();
724             badge.setBounds(w / 2, h / 2, w, h);
725             badge.draw(new Canvas(icon));
726         }
727         outIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON, icon);
728     }
729 
730     /**
731      * Adds this Icon to a Bundle that can be read back with the same parameters
732      * to {@link #createFromBundle(Bundle)}.
733      */
toBundle()734     public @NonNull Bundle toBundle() {
735         Bundle bundle = new Bundle();
736         switch (mType) {
737             case TYPE_BITMAP:
738             case TYPE_ADAPTIVE_BITMAP:
739                 bundle.putParcelable(EXTRA_OBJ, (Bitmap) mObj1);
740                 break;
741             case TYPE_UNKNOWN:
742                 // When unknown just wrapping an Icon.
743                 bundle.putParcelable(EXTRA_OBJ, (Parcelable) mObj1);
744                 break;
745             case TYPE_RESOURCE:
746             case TYPE_URI:
747             case TYPE_URI_ADAPTIVE_BITMAP:
748                 bundle.putString(EXTRA_OBJ, (String) mObj1);
749                 break;
750             case TYPE_DATA:
751                 bundle.putByteArray(EXTRA_OBJ, (byte[]) mObj1);
752                 break;
753             default:
754                 throw new IllegalArgumentException("Invalid icon");
755         }
756         bundle.putInt(EXTRA_TYPE, mType);
757         bundle.putInt(EXTRA_INT1, mInt1);
758         bundle.putInt(EXTRA_INT2, mInt2);
759         bundle.putString(EXTRA_STRING1, mString1);
760         if (mTintList != null) {
761             bundle.putParcelable(EXTRA_TINT_LIST, mTintList);
762         }
763         if (mTintMode != DEFAULT_TINT_MODE) {
764             bundle.putString(EXTRA_TINT_MODE, mTintMode.name());
765         }
766         return bundle;
767     }
768 
769     @Override
toString()770     public @NonNull String toString() {
771         if (mType == TYPE_UNKNOWN) {
772             return String.valueOf(mObj1);
773         }
774         final StringBuilder sb = new StringBuilder("Icon(typ=").append(typeToString(mType));
775         switch (mType) {
776             case TYPE_BITMAP:
777             case TYPE_ADAPTIVE_BITMAP:
778                 sb.append(" size=")
779                         .append(((Bitmap) mObj1).getWidth())
780                         .append("x")
781                         .append(((Bitmap) mObj1).getHeight());
782                 break;
783             case TYPE_RESOURCE:
784                 sb.append(" pkg=")
785                         .append(mString1)
786                         .append(" id=")
787                         .append(String.format("0x%08x", getResId()));
788                 break;
789             case TYPE_DATA:
790                 sb.append(" len=").append(mInt1);
791                 if (mInt2 != 0) {
792                     sb.append(" off=").append(mInt2);
793                 }
794                 break;
795             case TYPE_URI:
796             case TYPE_URI_ADAPTIVE_BITMAP:
797                 sb.append(" uri=").append(mObj1);
798                 break;
799         }
800         if (mTintList != null) {
801             sb.append(" tint=");
802             sb.append(mTintList);
803         }
804         if (mTintMode != DEFAULT_TINT_MODE) {
805             sb.append(" mode=").append(mTintMode);
806         }
807         sb.append(")");
808         return sb.toString();
809     }
810 
811     @Override
onPreParceling(boolean isStream)812     public void onPreParceling(boolean isStream) {
813         mTintModeStr = mTintMode.name();
814         switch (mType) {
815             case TYPE_UNKNOWN:
816                 if (isStream) {
817                     // We can't determine how to serialize this icon, so throw so the caller knows.
818                     throw new IllegalArgumentException("Can't serialize Icon created with "
819                             + "IconCompat#createFromIcon");
820                 } else {
821                     mParcelable = (Parcelable) mObj1;
822                 }
823                 break;
824             case TYPE_ADAPTIVE_BITMAP:
825             case TYPE_BITMAP:
826                 if (isStream) {
827                     Bitmap bitmap = (Bitmap) mObj1;
828                     ByteArrayOutputStream data = new ByteArrayOutputStream();
829                     bitmap.compress(Bitmap.CompressFormat.PNG, 90, data);
830                     mData = data.toByteArray();
831                 } else {
832                     mParcelable = (Parcelable) mObj1;
833                 }
834                 break;
835             case TYPE_URI:
836             case TYPE_URI_ADAPTIVE_BITMAP:
837                 mData = mObj1.toString().getBytes(Charset.forName("UTF-16"));
838                 break;
839             case TYPE_RESOURCE:
840                 mData = ((String) mObj1).getBytes(Charset.forName("UTF-16"));
841                 break;
842             case TYPE_DATA:
843                 mData = (byte[]) mObj1;
844                 break;
845         }
846     }
847 
848     @Override
onPostParceling()849     public void onPostParceling() {
850         mTintMode = PorterDuff.Mode.valueOf(mTintModeStr);
851         switch (mType) {
852             case TYPE_UNKNOWN:
853                 if (mParcelable != null) {
854                     mObj1 = mParcelable;
855                 } else {
856                     throw new IllegalArgumentException("Invalid icon");
857                 }
858                 break;
859             case TYPE_ADAPTIVE_BITMAP:
860             case TYPE_BITMAP:
861                 if (mParcelable != null) {
862                     mObj1 = mParcelable;
863                 } else {
864                     // This is data now.
865                     mObj1 = mData;
866                     mType = TYPE_DATA;
867                     mInt1 = 0;
868                     mInt2 = mData.length;
869                 }
870                 break;
871             case TYPE_URI:
872             case TYPE_URI_ADAPTIVE_BITMAP:
873             case TYPE_RESOURCE:
874                 mObj1 = new String(mData, Charset.forName("UTF-16"));
875                 // Slice, which may contain a IconCompat object, supports serialization to file.
876                 // In the old format, we don't store the app package name separately. To keep
877                 // the backward-compatibility, we have no choice but read the package name from the
878                 // full resource name string.
879                 if (mType == TYPE_RESOURCE) {
880                     if (mString1 == null) {
881                         mString1 = ((String) mObj1).split(":", -1)[0];
882                     }
883                 }
884                 break;
885             case TYPE_DATA:
886                 mObj1 = mData;
887                 break;
888         }
889     }
890 
typeToString(int x)891     private static String typeToString(int x) {
892         switch (x) {
893             case TYPE_BITMAP: return "BITMAP";
894             case TYPE_ADAPTIVE_BITMAP: return "BITMAP_MASKABLE";
895             case TYPE_DATA: return "DATA";
896             case TYPE_RESOURCE: return "RESOURCE";
897             case TYPE_URI: return "URI";
898             case TYPE_URI_ADAPTIVE_BITMAP: return "URI_MASKABLE";
899             default: return "UNKNOWN";
900         }
901     }
902 
903     /**
904      * Extracts an icon from a bundle that was added using {@link #toBundle()}.
905      */
906     @SuppressWarnings("deprecation")
createFromBundle(@onNull Bundle bundle)907     public static @Nullable IconCompat createFromBundle(@NonNull Bundle bundle) {
908         int type = bundle.getInt(EXTRA_TYPE);
909         IconCompat icon = new IconCompat(type);
910         icon.mInt1 = bundle.getInt(EXTRA_INT1);
911         icon.mInt2 = bundle.getInt(EXTRA_INT2);
912         icon.mString1 = bundle.getString(EXTRA_STRING1);
913         if (bundle.containsKey(EXTRA_TINT_LIST)) {
914             icon.mTintList = bundle.getParcelable(EXTRA_TINT_LIST);
915         }
916         if (bundle.containsKey(EXTRA_TINT_MODE)) {
917             icon.mTintMode = PorterDuff.Mode.valueOf(
918                     bundle.getString(EXTRA_TINT_MODE));
919         }
920         switch (type) {
921             case TYPE_BITMAP:
922             case TYPE_ADAPTIVE_BITMAP:
923             case TYPE_UNKNOWN:
924                 icon.mObj1 = bundle.getParcelable(EXTRA_OBJ);
925                 break;
926             case TYPE_RESOURCE:
927             case TYPE_URI:
928             case TYPE_URI_ADAPTIVE_BITMAP:
929                 icon.mObj1 = bundle.getString(EXTRA_OBJ);
930                 break;
931             case TYPE_DATA:
932                 icon.mObj1 = bundle.getByteArray(EXTRA_OBJ);
933                 break;
934             default:
935                 Log.w(TAG, "Unknown type " + type);
936                 return null;
937         }
938         return icon;
939     }
940 
941     /**
942      * Creates an IconCompat from an Icon.
943      */
944     @RequiresApi(23)
createFromIcon(@onNull Context context, @NonNull Icon icon)945     public static @NonNull IconCompat createFromIcon(@NonNull Context context,
946             @NonNull Icon icon) {
947         Preconditions.checkNotNull(icon);
948         return Api23Impl.createFromIcon(context, icon);
949     }
950 
951     /**
952      * Creates an IconCompat from an Icon.
953      */
954     @RestrictTo(LIBRARY_GROUP_PREFIX)
955     @RequiresApi(23)
createFromIcon(@onNull Icon icon)956     public static @NonNull IconCompat createFromIcon(@NonNull Icon icon) {
957         return Api23Impl.createFromIconInner(icon);
958     }
959 
960     /**
961      * Creates an IconCompat from an Icon, or returns null if the given Icon is created from
962      * resource 0.
963      */
964     @RestrictTo(LIBRARY_GROUP_PREFIX)
965     @RequiresApi(23)
createFromIconOrNullIfZeroResId(@onNull Icon icon)966     public static @Nullable IconCompat createFromIconOrNullIfZeroResId(@NonNull Icon icon) {
967         if (Api23Impl.getType(icon) == TYPE_RESOURCE && Api23Impl.getResId(icon) == 0) {
968             return null;
969         }
970         return Api23Impl.createFromIconInner(icon);
971     }
972 
973     /**
974      * Converts a bitmap following the adaptive icon guide lines, into a bitmap following the
975      * shortcut icon guide lines.
976      * The returned bitmap will always have same width and height and clipped to a circle.
977      *
978      * @param addShadow set to {@code true} only for legacy shortcuts and {@code false} otherwise
979      */
980     @VisibleForTesting
createLegacyIconFromAdaptiveIcon(Bitmap adaptiveIconBitmap, boolean addShadow)981     static Bitmap createLegacyIconFromAdaptiveIcon(Bitmap adaptiveIconBitmap, boolean addShadow) {
982         int size = (int) (DEFAULT_VIEW_PORT_SCALE * Math.min(adaptiveIconBitmap.getWidth(),
983                 adaptiveIconBitmap.getHeight()));
984 
985         Bitmap icon = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
986         Canvas canvas = new Canvas(icon);
987         Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
988 
989         float center = size * 0.5f;
990         float radius = center * ICON_DIAMETER_FACTOR;
991 
992         if (addShadow) {
993             // Draw key shadow
994             float blur = BLUR_FACTOR * size;
995             paint.setColor(Color.TRANSPARENT);
996             paint.setShadowLayer(blur, 0, KEY_SHADOW_OFFSET_FACTOR * size, KEY_SHADOW_ALPHA << 24);
997             canvas.drawCircle(center, center, radius, paint);
998 
999             // Draw ambient shadow
1000             paint.setShadowLayer(blur, 0, 0, AMBIENT_SHADOW_ALPHA << 24);
1001             canvas.drawCircle(center, center, radius, paint);
1002             paint.clearShadowLayer();
1003         }
1004 
1005         // Draw the clipped icon
1006         paint.setColor(Color.BLACK);
1007         BitmapShader shader = new BitmapShader(adaptiveIconBitmap, Shader.TileMode.CLAMP,
1008                 Shader.TileMode.CLAMP);
1009         Matrix shift = new Matrix();
1010         shift.setTranslate(-(adaptiveIconBitmap.getWidth() - size) / 2.0f,
1011                 -(adaptiveIconBitmap.getHeight() - size) / 2.0f);
1012         shader.setLocalMatrix(shift);
1013         paint.setShader(shader);
1014         canvas.drawCircle(center, center, radius, paint);
1015 
1016         canvas.setBitmap(null);
1017         return icon;
1018     }
1019 
1020     @RequiresApi(28)
1021     static class Api28Impl {
Api28Impl()1022         private Api28Impl() {
1023             // This class is not instantiable.
1024         }
1025 
getResPackage(Object icon)1026         static String getResPackage(Object icon) {
1027             return ((Icon) icon).getResPackage();
1028         }
1029 
getType(Object icon)1030         static int getType(Object icon) {
1031             return ((Icon) icon).getType();
1032         }
1033 
getResId(Object icon)1034         static int getResId(Object icon) {
1035             return ((Icon) icon).getResId();
1036         }
1037 
getUri(Object icon)1038         static Uri getUri(Object icon) {
1039             return ((Icon) icon).getUri();
1040         }
1041     }
1042 
1043     @RequiresApi(26)
1044     static class Api26Impl {
Api26Impl()1045         private Api26Impl() {
1046             // This class is not instantiable.
1047         }
1048 
createAdaptiveIconDrawable(Drawable backgroundDrawable, Drawable foregroundDrawable)1049         static Drawable createAdaptiveIconDrawable(Drawable backgroundDrawable,
1050                 Drawable foregroundDrawable) {
1051             return new AdaptiveIconDrawable(backgroundDrawable, foregroundDrawable);
1052         }
1053 
createWithAdaptiveBitmap(Bitmap bits)1054         static Icon createWithAdaptiveBitmap(Bitmap bits) {
1055             return Icon.createWithAdaptiveBitmap(bits);
1056         }
1057     }
1058 
1059     @RequiresApi(30)
1060     static class Api30Impl {
Api30Impl()1061         private Api30Impl() {
1062             // This class is not instantiable.
1063         }
1064 
createWithAdaptiveBitmapContentUri(Uri uri)1065         static Icon createWithAdaptiveBitmapContentUri(Uri uri) {
1066             return Icon.createWithAdaptiveBitmapContentUri(uri);
1067         }
1068 
1069     }
1070 
1071     @RequiresApi(23)
1072     static class Api23Impl {
Api23Impl()1073         private Api23Impl() {
1074             // This class is not instantiable.
1075         }
1076 
createFromIcon(@onNull Context context, @NonNull Icon icon)1077         static @NonNull IconCompat createFromIcon(@NonNull Context context, @NonNull Icon icon) {
1078             switch (getType(icon)) {
1079                 case TYPE_RESOURCE:
1080                     String resPackage = getResPackage(icon);
1081                     try {
1082                         return createWithResource(getResources(context, resPackage), resPackage,
1083                                 getResId(icon));
1084                     } catch (Resources.NotFoundException e) {
1085                         throw new IllegalArgumentException("Icon resource cannot be found");
1086                     }
1087                 case TYPE_URI:
1088                     return createWithContentUri(getUri(icon));
1089                 case TYPE_URI_ADAPTIVE_BITMAP:
1090                     return createWithAdaptiveBitmapContentUri(getUri(icon));
1091             }
1092             IconCompat iconCompat = new IconCompat(TYPE_UNKNOWN);
1093             iconCompat.mObj1 = icon;
1094             return iconCompat;
1095         }
1096 
1097         /**
1098          * Gets the type of the icon provided.
1099          * <p>
1100          * Note that new types may be added later, so callers should guard against other
1101          * types being returned. Returns {@link #TYPE_UNKNOWN} when the type cannot be
1102          * determined.
1103          */
1104         @IconType
getType(@onNull Object icon)1105         static int getType(@NonNull Object icon) {
1106             if (Build.VERSION.SDK_INT >= 28) {
1107                 return Api28Impl.getType(icon);
1108             } else {
1109                 try {
1110                     return (int) icon.getClass().getMethod("getType").invoke(icon);
1111                 } catch (IllegalAccessException e) {
1112                     Log.e(TAG, "Unable to get icon type " + icon, e);
1113                     return TYPE_UNKNOWN;
1114                 } catch (InvocationTargetException e) {
1115                     Log.e(TAG, "Unable to get icon type " + icon, e);
1116                     return TYPE_UNKNOWN;
1117                 } catch (NoSuchMethodException e) {
1118                     Log.e(TAG, "Unable to get icon type " + icon, e);
1119                     return TYPE_UNKNOWN;
1120                 }
1121             }
1122         }
1123 
1124         /**
1125          * Gets the package used to create this icon.
1126          * <p>
1127          * Only valid for icons of type TYPE_RESOURCE.
1128          * Note: This package may not be available if referenced in the future, and it is
1129          * up to the caller to ensure safety if this package is re-used and/or persisted.
1130          * Returns {@code null} when the value cannot be gotten.
1131          */
getResPackage(@onNull Object icon)1132         static @Nullable String getResPackage(@NonNull Object icon) {
1133             if (Build.VERSION.SDK_INT >= 28) {
1134                 return Api28Impl.getResPackage(icon);
1135             } else {
1136                 try {
1137                     return (String) icon.getClass().getMethod("getResPackage").invoke(icon);
1138                 } catch (IllegalAccessException e) {
1139                     Log.e(TAG, "Unable to get icon package", e);
1140                     return null;
1141                 } catch (InvocationTargetException e) {
1142                     Log.e(TAG, "Unable to get icon package", e);
1143                     return null;
1144                 } catch (NoSuchMethodException e) {
1145                     Log.e(TAG, "Unable to get icon package", e);
1146                     return null;
1147                 }
1148             }
1149         }
1150 
1151         /**
1152          * Used internally to avoid casting to Icon class in code accessible to SDK < 23.
1153          */
createFromIconInner(@onNull Object icon)1154         static IconCompat createFromIconInner(@NonNull Object icon) {
1155             Preconditions.checkNotNull(icon);
1156             switch (getType(icon)) {
1157                 case TYPE_RESOURCE:
1158                     return createWithResource(null, getResPackage(icon), getResId(icon));
1159                 case TYPE_URI:
1160                     return createWithContentUri(getUri(icon));
1161                 case TYPE_URI_ADAPTIVE_BITMAP:
1162                     return createWithAdaptiveBitmapContentUri(getUri(icon));
1163             }
1164             IconCompat iconCompat = new IconCompat(TYPE_UNKNOWN);
1165             iconCompat.mObj1 = icon;
1166             return iconCompat;
1167         }
1168 
1169         /**
1170          * Gets the resource used to create this icon.
1171          * <p>
1172          * Only valid for icons of type TYPE_RESOURCE.
1173          * Note: This resource may not be available if the application changes at all, and it is
1174          * up to the caller to ensure safety if this resource is re-used and/or persisted.
1175          * Returns {@code 0} if the id cannot be gotten.
1176          */
1177         @IdRes
1178         @DrawableRes
getResId(@onNull Object icon)1179         static int getResId(@NonNull Object icon) {
1180             if (Build.VERSION.SDK_INT >= 28) {
1181                 return Api28Impl.getResId(icon);
1182             } else {
1183                 try {
1184                     return (int) icon.getClass().getMethod("getResId").invoke(icon);
1185                 } catch (IllegalAccessException e) {
1186                     Log.e(TAG, "Unable to get icon resource", e);
1187                     return 0;
1188                 } catch (InvocationTargetException e) {
1189                     Log.e(TAG, "Unable to get icon resource", e);
1190                     return 0;
1191                 } catch (NoSuchMethodException e) {
1192                     Log.e(TAG, "Unable to get icon resource", e);
1193                     return 0;
1194                 }
1195             }
1196         }
1197 
1198         /**
1199          * Gets the uri used to create this icon.
1200          * <p>
1201          * Only valid for icons of type TYPE_URI.
1202          * Note: This uri may not be available in the future, and it is
1203          * up to the caller to ensure safety if this uri is re-used and/or persisted.
1204          * Returns {@code null} if the uri cannot be gotten.
1205          */
getUri(@onNull Object icon)1206         static @Nullable Uri getUri(@NonNull Object icon) {
1207             if (Build.VERSION.SDK_INT >= 28) {
1208                 return Api28Impl.getUri(icon);
1209             } else {
1210                 try {
1211                     return (Uri) icon.getClass().getMethod("getUri").invoke(icon);
1212                 } catch (IllegalAccessException e) {
1213                     Log.e(TAG, "Unable to get icon uri", e);
1214                     return null;
1215                 } catch (InvocationTargetException e) {
1216                     Log.e(TAG, "Unable to get icon uri", e);
1217                     return null;
1218                 } catch (NoSuchMethodException e) {
1219                     Log.e(TAG, "Unable to get icon uri", e);
1220                     return null;
1221                 }
1222             }
1223         }
1224 
toIcon(IconCompat iconCompat, Context context)1225         static Icon toIcon(IconCompat iconCompat, Context context) {
1226             Icon icon;
1227             switch (iconCompat.mType) {
1228                 case TYPE_UNKNOWN:
1229                     // When type is unknown we are just wrapping an icon.
1230                     return (Icon) iconCompat.mObj1;
1231                 case TYPE_BITMAP:
1232                     icon = Icon.createWithBitmap((Bitmap) iconCompat.mObj1);
1233                     break;
1234                 case TYPE_ADAPTIVE_BITMAP:
1235                     if (Build.VERSION.SDK_INT >= 26) {
1236                         icon = Api26Impl.createWithAdaptiveBitmap((Bitmap) iconCompat.mObj1);
1237                     } else {
1238                         icon = Icon.createWithBitmap(
1239                                 createLegacyIconFromAdaptiveIcon((Bitmap) iconCompat.mObj1, false));
1240                     }
1241                     break;
1242                 case TYPE_RESOURCE:
1243                     icon = Icon.createWithResource(iconCompat.getResPackage(), iconCompat.mInt1);
1244                     break;
1245                 case TYPE_DATA:
1246                     icon = Icon.createWithData((byte[]) iconCompat.mObj1, iconCompat.mInt1,
1247                             iconCompat.mInt2);
1248                     break;
1249                 case TYPE_URI:
1250                     icon = Icon.createWithContentUri((String) iconCompat.mObj1);
1251                     break;
1252                 case TYPE_URI_ADAPTIVE_BITMAP:
1253                     if (Build.VERSION.SDK_INT >= 30) {
1254                         icon = Api30Impl.createWithAdaptiveBitmapContentUri(iconCompat.getUri());
1255                         break;
1256                     }
1257                     if (context == null) {
1258                         throw new IllegalArgumentException(
1259                                 "Context is required to resolve the file uri of the icon: "
1260                                         + iconCompat.getUri());
1261                     }
1262                     InputStream is = iconCompat.getUriInputStream(context);
1263                     if (is == null) {
1264                         throw new IllegalStateException(
1265                                 "Cannot load adaptive icon from uri: " + iconCompat.getUri());
1266                     }
1267                     if (Build.VERSION.SDK_INT >= 26) {
1268                         icon = Api26Impl.createWithAdaptiveBitmap(BitmapFactory.decodeStream(is));
1269                     } else {
1270                         icon = Icon.createWithBitmap(createLegacyIconFromAdaptiveIcon(
1271                                 BitmapFactory.decodeStream(is), false));
1272                     }
1273                     break;
1274                 default:
1275                     throw new IllegalArgumentException("Unknown type");
1276             }
1277             if (iconCompat.mTintList != null) {
1278                 icon.setTintList(iconCompat.mTintList);
1279             }
1280             if (iconCompat.mTintMode != DEFAULT_TINT_MODE) {
1281                 icon.setTintMode(iconCompat.mTintMode);
1282             }
1283             return icon;
1284         }
1285 
loadDrawable(Icon icon, Context context)1286         static Drawable loadDrawable(Icon icon, Context context) {
1287             return icon.loadDrawable(context);
1288         }
1289     }
1290 }
1291