• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.graphics.drawable.Icon.TYPE_ADAPTIVE_BITMAP;
20 import static android.graphics.drawable.Icon.TYPE_BITMAP;
21 import static android.graphics.drawable.Icon.TYPE_DATA;
22 import static android.graphics.drawable.Icon.TYPE_RESOURCE;
23 import static android.graphics.drawable.Icon.TYPE_URI;
24 
25 import static androidx.annotation.RestrictTo.Scope.LIBRARY;
26 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
27 
28 import android.app.ActivityManager;
29 import android.content.ContentResolver;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.pm.ApplicationInfo;
33 import android.content.pm.PackageManager;
34 import android.content.res.ColorStateList;
35 import android.content.res.Resources;
36 import android.graphics.Bitmap;
37 import android.graphics.BitmapFactory;
38 import android.graphics.BitmapShader;
39 import android.graphics.Canvas;
40 import android.graphics.Color;
41 import android.graphics.Matrix;
42 import android.graphics.Paint;
43 import android.graphics.PorterDuff;
44 import android.graphics.Shader;
45 import android.graphics.drawable.BitmapDrawable;
46 import android.graphics.drawable.Drawable;
47 import android.graphics.drawable.Icon;
48 import android.net.Uri;
49 import android.os.Build;
50 import android.os.Bundle;
51 import android.os.Parcelable;
52 import android.text.TextUtils;
53 import android.util.Log;
54 
55 import androidx.annotation.ColorInt;
56 import androidx.annotation.DrawableRes;
57 import androidx.annotation.IdRes;
58 import androidx.annotation.IntDef;
59 import androidx.annotation.NonNull;
60 import androidx.annotation.Nullable;
61 import androidx.annotation.RequiresApi;
62 import androidx.annotation.RestrictTo;
63 import androidx.annotation.VisibleForTesting;
64 import androidx.core.content.ContextCompat;
65 import androidx.core.content.res.ResourcesCompat;
66 import androidx.core.os.BuildCompat;
67 
68 import java.io.File;
69 import java.io.FileInputStream;
70 import java.io.FileNotFoundException;
71 import java.io.InputStream;
72 import java.lang.annotation.Retention;
73 import java.lang.annotation.RetentionPolicy;
74 import java.lang.reflect.InvocationTargetException;
75 
76 /**
77  * Helper for accessing features in {@link android.graphics.drawable.Icon}.
78  */
79 public class IconCompat {
80 
81     private static final String TAG = "IconCompat";
82 
83     /**
84      * Value returned when the type of an {@link Icon} cannot be determined.
85      * @see #getType(Icon)
86      */
87     public static final int TYPE_UNKOWN = -1;
88 
89     /**
90      * @hide
91      */
92     @RestrictTo(LIBRARY)
93     @IntDef({TYPE_UNKOWN, TYPE_BITMAP, TYPE_RESOURCE, TYPE_DATA, TYPE_URI, TYPE_ADAPTIVE_BITMAP})
94     @Retention(RetentionPolicy.SOURCE)
95     public @interface IconType {
96     }
97 
98     // Ratio of expected size to actual icon size
99     private static final float ADAPTIVE_ICON_INSET_FACTOR = 1 / 4f;
100     private static final float DEFAULT_VIEW_PORT_SCALE = 1 / (1 + 2 * ADAPTIVE_ICON_INSET_FACTOR);
101     private static final float ICON_DIAMETER_FACTOR = 176f / 192;
102     private static final float BLUR_FACTOR = 0.5f / 48;
103     private static final float KEY_SHADOW_OFFSET_FACTOR = 1f / 48;
104 
105     private static final int KEY_SHADOW_ALPHA = 61;
106     private static final int AMBIENT_SHADOW_ALPHA = 30;
107 
108     private static final String EXTRA_TYPE = "type";
109     private static final String EXTRA_OBJ = "obj";
110     private static final String EXTRA_INT1 = "int1";
111     private static final String EXTRA_INT2 = "int2";
112     private static final String EXTRA_TINT_LIST = "tint_list";
113     private static final String EXTRA_TINT_MODE = "tint_mode";
114 
115     private final int mType;
116 
117     // To avoid adding unnecessary overhead, we have a few basic objects that get repurposed
118     // based on the value of mType.
119 
120     // TYPE_BITMAP: Bitmap
121     // TYPE_ADAPTIVE_BITMAP: Bitmap
122     // TYPE_RESOURCE: Context
123     // TYPE_URI: String
124     // TYPE_DATA: DataBytes
125     private Object          mObj1;
126 
127     // TYPE_RESOURCE: resId
128     // TYPE_DATA: data offset
129     private int             mInt1;
130 
131     // TYPE_DATA: data length
132     private int             mInt2;
133 
134     private ColorStateList  mTintList = null;
135     static final PorterDuff.Mode DEFAULT_TINT_MODE = PorterDuff.Mode.SRC_IN; // SRC_IN
136     private PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE;
137 
138     /**
139      * Create an Icon pointing to a drawable resource.
140      * @param context The context for the application whose resources should be used to resolve the
141      *                given resource ID.
142      * @param resId ID of the drawable resource
143      * @see android.graphics.drawable.Icon#createWithResource(Context, int)
144      */
createWithResource(Context context, @DrawableRes int resId)145     public static IconCompat createWithResource(Context context, @DrawableRes int resId) {
146         if (context == null) {
147             throw new IllegalArgumentException("Context must not be null.");
148         }
149         final IconCompat rep = new IconCompat(TYPE_RESOURCE);
150         rep.mInt1 = resId;
151         rep.mObj1 = context.getPackageName();
152         return rep;
153     }
154 
155     /**
156      * Create an Icon pointing to a bitmap in memory.
157      * @param bits A valid {@link android.graphics.Bitmap} object
158      * @see android.graphics.drawable.Icon#createWithBitmap(Bitmap)
159      */
createWithBitmap(Bitmap bits)160     public static IconCompat createWithBitmap(Bitmap bits) {
161         if (bits == null) {
162             throw new IllegalArgumentException("Bitmap must not be null.");
163         }
164         final IconCompat rep = new IconCompat(TYPE_BITMAP);
165         rep.mObj1 = bits;
166         return rep;
167     }
168 
169     /**
170      * Create an Icon pointing to a bitmap in memory that follows the icon design guideline defined
171      * by {@link android.graphics.drawable.AdaptiveIconDrawable}.
172      * @param bits A valid {@link android.graphics.Bitmap} object
173      * @see android.graphics.drawable.Icon#createWithAdaptiveBitmap(Bitmap)
174      */
createWithAdaptiveBitmap(Bitmap bits)175     public static IconCompat createWithAdaptiveBitmap(Bitmap bits) {
176         if (bits == null) {
177             throw new IllegalArgumentException("Bitmap must not be null.");
178         }
179         final IconCompat rep = new IconCompat(TYPE_ADAPTIVE_BITMAP);
180         rep.mObj1 = bits;
181         return rep;
182     }
183 
184     /**
185      * Create an Icon pointing to a compressed bitmap stored in a byte array.
186      * @param data Byte array storing compressed bitmap data of a type that
187      *             {@link android.graphics.BitmapFactory}
188      *             can decode (see {@link android.graphics.Bitmap.CompressFormat}).
189      * @param offset Offset into <code>data</code> at which the bitmap data starts
190      * @param length Length of the bitmap data
191      * @see android.graphics.drawable.Icon#createWithData(byte[], int, int)
192      */
createWithData(byte[] data, int offset, int length)193     public static IconCompat createWithData(byte[] data, int offset, int length) {
194         if (data == null) {
195             throw new IllegalArgumentException("Data must not be null.");
196         }
197         final IconCompat rep = new IconCompat(TYPE_DATA);
198         rep.mObj1 = data;
199         rep.mInt1 = offset;
200         rep.mInt2 = length;
201         return rep;
202     }
203 
204     /**
205      * Create an Icon pointing to an image file specified by URI.
206      *
207      * @param uri A uri referring to local content:// or file:// image data.
208      * @see android.graphics.drawable.Icon#createWithContentUri(String)
209      */
createWithContentUri(String uri)210     public static IconCompat createWithContentUri(String uri) {
211         if (uri == null) {
212             throw new IllegalArgumentException("Uri must not be null.");
213         }
214         final IconCompat rep = new IconCompat(TYPE_URI);
215         rep.mObj1 = uri;
216         return rep;
217     }
218 
219     /**
220      * Create an Icon pointing to an image file specified by URI.
221      *
222      * @param uri A uri referring to local content:// or file:// image data.
223      * @see android.graphics.drawable.Icon#createWithContentUri(String)
224      */
createWithContentUri(Uri uri)225     public static IconCompat createWithContentUri(Uri uri) {
226         if (uri == null) {
227             throw new IllegalArgumentException("Uri must not be null.");
228         }
229         return createWithContentUri(uri.toString());
230     }
231 
IconCompat(int mType)232     private IconCompat(int mType) {
233         this.mType = mType;
234     }
235 
236 
237     /**
238      * Gets the type of the icon provided.
239      * <p>
240      * Note that new types may be added later, so callers should guard against other
241      * types being returned.
242      */
243     @IconType
getType()244     public int getType() {
245         if (mType == TYPE_UNKOWN && Build.VERSION.SDK_INT >= 23) {
246             return getType((Icon) mObj1);
247         }
248         return mType;
249     }
250 
251     /**
252      * Gets the package used to create this icon.
253      * <p>
254      * Only valid for icons of type TYPE_RESOURCE.
255      * Note: This package may not be available if referenced in the future, and it is
256      * up to the caller to ensure safety if this package is re-used and/or persisted.
257      */
258     @NonNull
getResPackage()259     public String getResPackage() {
260         if (mType == TYPE_UNKOWN && Build.VERSION.SDK_INT >= 23) {
261             return getResPackage((Icon) mObj1);
262         }
263         if (mType != TYPE_RESOURCE) {
264             throw new IllegalStateException("called getResPackage() on " + this);
265         }
266         return (String) mObj1;
267     }
268 
269     /**
270      * Gets the resource id used to create this icon.
271      * <p>
272      * Only valid for icons of type TYPE_RESOURCE.
273      * Note: This resource may not be available if the application changes at all, and it is
274      * up to the caller to ensure safety if this resource is re-used and/or persisted.
275      */
276     @IdRes
getResId()277     public int getResId() {
278         if (mType == TYPE_UNKOWN && Build.VERSION.SDK_INT >= 23) {
279             return getResId((Icon) mObj1);
280         }
281         if (mType != TYPE_RESOURCE) {
282             throw new IllegalStateException("called getResId() on " + this);
283         }
284         return mInt1;
285     }
286 
287     /**
288      * Gets the uri used to create this icon.
289      * <p>
290      * Only valid for icons of type TYPE_URI.
291      * Note: This uri may not be available in the future, and it is
292      * up to the caller to ensure safety if this uri is re-used and/or persisted.
293      */
294     @NonNull
getUri()295     public Uri getUri() {
296         if (mType == TYPE_UNKOWN && Build.VERSION.SDK_INT >= 23) {
297             return getUri((Icon) mObj1);
298         }
299         return Uri.parse((String) mObj1);
300     }
301 
302     /**
303      * Store a color to use whenever this Icon is drawn.
304      *
305      * @param tint a color, as in {@link Drawable#setTint(int)}
306      * @return this same object, for use in chained construction
307      */
setTint(@olorInt int tint)308     public IconCompat setTint(@ColorInt int tint) {
309         return setTintList(ColorStateList.valueOf(tint));
310     }
311 
312     /**
313      * Store a color to use whenever this Icon is drawn.
314      *
315      * @param tintList as in {@link Drawable#setTintList(ColorStateList)}, null to remove tint
316      * @return this same object, for use in chained construction
317      */
setTintList(ColorStateList tintList)318     public IconCompat setTintList(ColorStateList tintList) {
319         mTintList = tintList;
320         return this;
321     }
322 
323     /**
324      * Store a blending mode to use whenever this Icon is drawn.
325      *
326      * @param mode a blending mode, as in {@link Drawable#setTintMode(PorterDuff.Mode)}, may be null
327      * @return this same object, for use in chained construction
328      */
setTintMode(PorterDuff.Mode mode)329     public IconCompat setTintMode(PorterDuff.Mode mode) {
330         mTintMode = mode;
331         return this;
332     }
333 
334     /**
335      * Convert this compat object to {@link Icon} object.
336      *
337      * @return {@link Icon} object
338      */
339     @RequiresApi(23)
toIcon()340     public Icon toIcon() {
341         Icon icon;
342         switch (mType) {
343             case TYPE_UNKOWN:
344                 // When type is unknown we are just wrapping an icon.
345                 return (Icon) mObj1;
346             case TYPE_BITMAP:
347                 icon = Icon.createWithBitmap((Bitmap) mObj1);
348                 break;
349             case TYPE_ADAPTIVE_BITMAP:
350                 if (Build.VERSION.SDK_INT >= 26) {
351                     icon = Icon.createWithAdaptiveBitmap((Bitmap) mObj1);
352                 } else {
353                     icon = Icon.createWithBitmap(
354                             createLegacyIconFromAdaptiveIcon((Bitmap) mObj1, false));
355                 }
356                 break;
357             case TYPE_RESOURCE:
358                 icon = Icon.createWithResource((String) mObj1, mInt1);
359                 break;
360             case TYPE_DATA:
361                 icon = Icon.createWithData((byte[]) mObj1, mInt1, mInt2);
362                 break;
363             case TYPE_URI:
364                 icon = Icon.createWithContentUri((String) mObj1);
365                 break;
366             default:
367                 throw new IllegalArgumentException("Unknown type");
368         }
369         if (mTintList != null) {
370             icon.setTintList(mTintList);
371         }
372         if (mTintMode != DEFAULT_TINT_MODE) {
373             icon.setTintMode(mTintMode);
374         }
375         return icon;
376     }
377 
378 
379 
380     /**
381      * Returns a Drawable that can be used to draw the image inside this Icon, constructing it
382      * if necessary.
383      *
384      * @param context {@link android.content.Context Context} in which to load the drawable; used
385      *                to access {@link android.content.res.Resources Resources}, for example.
386      * @return A fresh instance of a drawable for this image, yours to keep.
387      */
loadDrawable(Context context)388     public Drawable loadDrawable(Context context) {
389         if (Build.VERSION.SDK_INT >= 23) {
390             return toIcon().loadDrawable(context);
391         }
392         final Drawable result = loadDrawableInner(context);
393         if (result != null && (mTintList != null || mTintMode != DEFAULT_TINT_MODE)) {
394             result.mutate();
395             DrawableCompat.setTintList(result, mTintList);
396             DrawableCompat.setTintMode(result, mTintMode);
397         }
398         return result;
399     }
400 
401 
402     /**
403      * Do the heavy lifting of loading the drawable, but stop short of applying any tint.
404      */
loadDrawableInner(Context context)405     private Drawable loadDrawableInner(Context context) {
406         switch (mType) {
407             case TYPE_BITMAP:
408                 return new BitmapDrawable(context.getResources(), (Bitmap) mObj1);
409             case TYPE_ADAPTIVE_BITMAP:
410                 return new BitmapDrawable(context.getResources(),
411                         createLegacyIconFromAdaptiveIcon((Bitmap) mObj1, false));
412             case TYPE_RESOURCE:
413                 Resources res;
414                 // figure out where to load resources from
415                 String resPackage = (String) mObj1;
416                 if (TextUtils.isEmpty(resPackage)) {
417                     // if none is specified, try the given context
418                     resPackage = context.getPackageName();
419                 }
420                 if ("android".equals(resPackage)) {
421                     res = Resources.getSystem();
422                 } else {
423                     final PackageManager pm = context.getPackageManager();
424                     try {
425                         ApplicationInfo ai = pm.getApplicationInfo(
426                                 resPackage, PackageManager.MATCH_UNINSTALLED_PACKAGES);
427                         if (ai != null) {
428                             res = pm.getResourcesForApplication(ai);
429                         } else {
430                             break;
431                         }
432                     } catch (PackageManager.NameNotFoundException e) {
433                         Log.e(TAG, String.format("Unable to find pkg=%s for icon %s",
434                                 resPackage, this), e);
435                         break;
436                     }
437                 }
438                 try {
439                     return ResourcesCompat.getDrawable(res, mInt1, context.getTheme());
440                 } catch (RuntimeException e) {
441                     Log.e(TAG, String.format("Unable to load resource 0x%08x from pkg=%s",
442                             mInt1,
443                             mObj1),
444                             e);
445                 }
446                 break;
447             case TYPE_DATA:
448                 return new BitmapDrawable(context.getResources(),
449                         BitmapFactory.decodeByteArray((byte[]) mObj1, mInt1, mInt2)
450                 );
451             case TYPE_URI:
452                 final Uri uri = Uri.parse((String) mObj1);
453                 final String scheme = uri.getScheme();
454                 InputStream is = null;
455                 if (ContentResolver.SCHEME_CONTENT.equals(scheme)
456                         || ContentResolver.SCHEME_FILE.equals(scheme)) {
457                     try {
458                         is = context.getContentResolver().openInputStream(uri);
459                     } catch (Exception e) {
460                         Log.w(TAG, "Unable to load image from URI: " + uri, e);
461                     }
462                 } else {
463                     try {
464                         is = new FileInputStream(new File((String) mObj1));
465                     } catch (FileNotFoundException e) {
466                         Log.w(TAG, "Unable to load image from path: " + uri, e);
467                     }
468                 }
469                 if (is != null) {
470                     return new BitmapDrawable(context.getResources(),
471                             BitmapFactory.decodeStream(is));
472                 }
473                 break;
474         }
475         return null;
476     }
477 
478     /**
479      * @hide
480      */
481     @RestrictTo(LIBRARY_GROUP)
482     @SuppressWarnings("deprecation")
addToShortcutIntent(@onNull Intent outIntent, @Nullable Drawable badge, @NonNull Context c)483     public void addToShortcutIntent(@NonNull Intent outIntent, @Nullable Drawable badge,
484             @NonNull Context c) {
485         Bitmap icon;
486         switch (mType) {
487             case TYPE_BITMAP:
488                 icon = (Bitmap) mObj1;
489                 if (badge != null) {
490                     // Do not modify the original icon when applying a badge
491                     icon = icon.copy(icon.getConfig(), true);
492                 }
493                 break;
494             case TYPE_ADAPTIVE_BITMAP:
495                 icon = createLegacyIconFromAdaptiveIcon((Bitmap) mObj1, true);
496                 break;
497             case TYPE_RESOURCE:
498                 try {
499                     Context context = c.createPackageContext((String) mObj1, 0);
500                     if (badge == null) {
501                         outIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
502                                 Intent.ShortcutIconResource.fromContext(context, mInt1));
503                         return;
504                     } else {
505                         Drawable dr = ContextCompat.getDrawable(context, mInt1);
506                         if (dr.getIntrinsicWidth() <= 0 || dr.getIntrinsicHeight() <= 0) {
507                             int size = ((ActivityManager) context.getSystemService(
508                                     Context.ACTIVITY_SERVICE)).getLauncherLargeIconSize();
509                             icon = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
510                         } else {
511                             icon = Bitmap.createBitmap(dr.getIntrinsicWidth(),
512                                     dr.getIntrinsicHeight(),
513                                     Bitmap.Config.ARGB_8888);
514                         }
515                         dr.setBounds(0, 0, icon.getWidth(), icon.getHeight());
516                         dr.draw(new Canvas(icon));
517                     }
518                 } catch (PackageManager.NameNotFoundException e) {
519                     throw new IllegalArgumentException("Can't find package " + mObj1, e);
520                 }
521                 break;
522             default:
523                 throw new IllegalArgumentException("Icon type not supported for intent shortcuts");
524         }
525         if (badge != null) {
526             // Badge the icon
527             int w = icon.getWidth();
528             int h = icon.getHeight();
529             badge.setBounds(w / 2, h / 2, w, h);
530             badge.draw(new Canvas(icon));
531         }
532         outIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON, icon);
533     }
534 
535     /**
536      * Adds this Icon to a Bundle that can be read back with the same parameters
537      * to {@link #createFromBundle(Bundle)}.
538      */
toBundle()539     public Bundle toBundle() {
540         Bundle bundle = new Bundle();
541         switch (mType) {
542             case TYPE_BITMAP:
543             case TYPE_ADAPTIVE_BITMAP:
544                 bundle.putParcelable(EXTRA_OBJ, (Bitmap) mObj1);
545                 break;
546             case TYPE_UNKOWN:
547                 // When unknown just wrapping an Icon.
548                 bundle.putParcelable(EXTRA_OBJ, (Parcelable) mObj1);
549                 break;
550             case TYPE_RESOURCE:
551             case TYPE_URI:
552                 bundle.putString(EXTRA_OBJ, (String) mObj1);
553                 break;
554             case TYPE_DATA:
555                 bundle.putByteArray(EXTRA_OBJ, (byte[]) mObj1);
556                 break;
557             default:
558                 throw new IllegalArgumentException("Invalid icon");
559         }
560         bundle.putInt(EXTRA_TYPE, mType);
561         bundle.putInt(EXTRA_INT1, mInt1);
562         bundle.putInt(EXTRA_INT2, mInt2);
563         if (mTintList != null) {
564             bundle.putParcelable(EXTRA_TINT_LIST, mTintList);
565         }
566         if (mTintMode != DEFAULT_TINT_MODE) {
567             bundle.putString(EXTRA_TINT_MODE, mTintMode.name());
568         }
569         return bundle;
570     }
571 
572     @Override
toString()573     public String toString() {
574         if (mType == TYPE_UNKOWN) {
575             return String.valueOf(mObj1);
576         }
577         final StringBuilder sb = new StringBuilder("Icon(typ=").append(typeToString(mType));
578         switch (mType) {
579             case TYPE_BITMAP:
580             case TYPE_ADAPTIVE_BITMAP:
581                 sb.append(" size=")
582                         .append(((Bitmap) mObj1).getWidth())
583                         .append("x")
584                         .append(((Bitmap) mObj1).getHeight());
585                 break;
586             case TYPE_RESOURCE:
587                 sb.append(" pkg=")
588                         .append(getResPackage())
589                         .append(" id=")
590                         .append(String.format("0x%08x", getResId()));
591                 break;
592             case TYPE_DATA:
593                 sb.append(" len=").append(mInt1);
594                 if (mInt2 != 0) {
595                     sb.append(" off=").append(mInt2);
596                 }
597                 break;
598             case TYPE_URI:
599                 sb.append(" uri=").append(mObj1);
600                 break;
601         }
602         if (mTintList != null) {
603             sb.append(" tint=");
604             sb.append(mTintList);
605         }
606         if (mTintMode != DEFAULT_TINT_MODE) {
607             sb.append(" mode=").append(mTintMode);
608         }
609         sb.append(")");
610         return sb.toString();
611     }
612 
typeToString(int x)613     private static String typeToString(int x) {
614         switch (x) {
615             case TYPE_BITMAP: return "BITMAP";
616             case TYPE_ADAPTIVE_BITMAP: return "BITMAP_MASKABLE";
617             case TYPE_DATA: return "DATA";
618             case TYPE_RESOURCE: return "RESOURCE";
619             case TYPE_URI: return "URI";
620             default: return "UNKNOWN";
621         }
622     }
623 
624     /**
625      * Extracts an icon from a bundle that was added using {@link #toBundle()}.
626      */
createFromBundle(@onNull Bundle bundle)627     public static @Nullable IconCompat createFromBundle(@NonNull Bundle bundle) {
628         int type = bundle.getInt(EXTRA_TYPE);
629         IconCompat icon = new IconCompat(type);
630         icon.mInt1 = bundle.getInt(EXTRA_INT1);
631         icon.mInt2 = bundle.getInt(EXTRA_INT2);
632         if (bundle.containsKey(EXTRA_TINT_LIST)) {
633             icon.mTintList = bundle.getParcelable(EXTRA_TINT_LIST);
634         }
635         if (bundle.containsKey(EXTRA_TINT_MODE)) {
636             icon.mTintMode = PorterDuff.Mode.valueOf(
637                     bundle.getString(EXTRA_TINT_MODE));
638         }
639         switch (type) {
640             case TYPE_BITMAP:
641             case TYPE_ADAPTIVE_BITMAP:
642             case TYPE_UNKOWN:
643                 icon.mObj1 = bundle.getParcelable(EXTRA_OBJ);
644                 break;
645             case TYPE_RESOURCE:
646             case TYPE_URI:
647                 icon.mObj1 = bundle.getString(EXTRA_OBJ);
648                 break;
649             case TYPE_DATA:
650                 icon.mObj1 = bundle.getByteArray(EXTRA_OBJ);
651                 break;
652             default:
653                 Log.w(TAG, "Unknown type " + type);
654                 return null;
655         }
656         return icon;
657     }
658 
659     /**
660      * Creates an IconCompat from an Icon.
661      */
662     @RequiresApi(23)
663     @Nullable
createFromIcon(@onNull Icon icon)664     public static IconCompat createFromIcon(@NonNull Icon icon) {
665         IconCompat iconCompat = new IconCompat(TYPE_UNKOWN);
666         iconCompat.mObj1 = icon;
667         return iconCompat;
668     }
669 
670     /**
671      * Gets the type of the icon provided.
672      * <p>
673      * Note that new types may be added later, so callers should guard against other
674      * types being returned. Returns {@link #TYPE_UNKOWN} when the type cannot be
675      * determined.
676      */
677     @IconType
678     @RequiresApi(23)
getType(@onNull Icon icon)679     public static int getType(@NonNull Icon icon) {
680         if (BuildCompat.isAtLeastP()) {
681             return icon.getType();
682         }
683         try {
684             return (int) icon.getClass().getMethod("getType").invoke(icon);
685         } catch (IllegalAccessException e) {
686             Log.e(TAG, "Unable to get icon type " + icon, e);
687             return TYPE_UNKOWN;
688         } catch (InvocationTargetException e) {
689             Log.e(TAG, "Unable to get icon type " + icon, e);
690             return TYPE_UNKOWN;
691         } catch (NoSuchMethodException e) {
692             Log.e(TAG, "Unable to get icon type " + icon, e);
693             return TYPE_UNKOWN;
694         }
695     }
696 
697     /**
698      * Gets the package used to create this icon.
699      * <p>
700      * Only valid for icons of type TYPE_RESOURCE.
701      * Note: This package may not be available if referenced in the future, and it is
702      * up to the caller to ensure safety if this package is re-used and/or persisted.
703      * Returns {@code null} when the value cannot be gotten.
704      */
705     @Nullable
706     @RequiresApi(23)
getResPackage(@onNull Icon icon)707     public static String getResPackage(@NonNull Icon icon) {
708         if (BuildCompat.isAtLeastP()) {
709             return icon.getResPackage();
710         }
711         try {
712             return (String) icon.getClass().getMethod("getResPackage").invoke(icon);
713         } catch (IllegalAccessException e) {
714             Log.e(TAG, "Unable to get icon package", e);
715             return null;
716         } catch (InvocationTargetException e) {
717             Log.e(TAG, "Unable to get icon package", e);
718             return null;
719         } catch (NoSuchMethodException e) {
720             Log.e(TAG, "Unable to get icon package", e);
721             return null;
722         }
723     }
724 
725     /**
726      * Gets the resource used to create this icon.
727      * <p>
728      * Only valid for icons of type TYPE_RESOURCE.
729      * Note: This resource may not be available if the application changes at all, and it is
730      * up to the caller to ensure safety if this resource is re-used and/or persisted.
731      * Returns {@code 0} if the id cannot be gotten.
732      */
733     @IdRes
734     @RequiresApi(23)
getResId(@onNull Icon icon)735     public static int getResId(@NonNull Icon icon) {
736         if (BuildCompat.isAtLeastP()) {
737             return icon.getResId();
738         }
739         try {
740             return (int) icon.getClass().getMethod("getResId").invoke(icon);
741         } catch (IllegalAccessException e) {
742             Log.e(TAG, "Unable to get icon resource", e);
743             return 0;
744         } catch (InvocationTargetException e) {
745             Log.e(TAG, "Unable to get icon resource", e);
746             return 0;
747         } catch (NoSuchMethodException e) {
748             Log.e(TAG, "Unable to get icon resource", e);
749             return 0;
750         }
751     }
752 
753     /**
754      * Gets the uri used to create this icon.
755      * <p>
756      * Only valid for icons of type TYPE_URI.
757      * Note: This uri may not be available in the future, and it is
758      * up to the caller to ensure safety if this uri is re-used and/or persisted.
759      * Returns {@code null} if the uri cannot be gotten.
760      */
761     @Nullable
762     @RequiresApi(23)
getUri(@onNull Icon icon)763     public Uri getUri(@NonNull Icon icon) {
764         if (BuildCompat.isAtLeastP()) {
765             return icon.getUri();
766         }
767         try {
768             return (Uri) icon.getClass().getMethod("getUri").invoke(icon);
769         } catch (IllegalAccessException e) {
770             Log.e(TAG, "Unable to get icon uri", e);
771             return null;
772         } catch (InvocationTargetException e) {
773             Log.e(TAG, "Unable to get icon uri", e);
774             return null;
775         } catch (NoSuchMethodException e) {
776             Log.e(TAG, "Unable to get icon uri", e);
777             return null;
778         }
779     }
780 
781     /**
782      * Converts a bitmap following the adaptive icon guide lines, into a bitmap following the
783      * shortcut icon guide lines.
784      * The returned bitmap will always have same width and height and clipped to a circle.
785      *
786      * @param addShadow set to {@code true} only for legacy shortcuts and {@code false} otherwise
787      */
788     @VisibleForTesting
createLegacyIconFromAdaptiveIcon(Bitmap adaptiveIconBitmap, boolean addShadow)789     static Bitmap createLegacyIconFromAdaptiveIcon(Bitmap adaptiveIconBitmap, boolean addShadow) {
790         int size = (int) (DEFAULT_VIEW_PORT_SCALE * Math.min(adaptiveIconBitmap.getWidth(),
791                 adaptiveIconBitmap.getHeight()));
792 
793         Bitmap icon = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
794         Canvas canvas = new Canvas(icon);
795         Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
796 
797         float center = size * 0.5f;
798         float radius = center * ICON_DIAMETER_FACTOR;
799 
800         if (addShadow) {
801             // Draw key shadow
802             float blur = BLUR_FACTOR * size;
803             paint.setColor(Color.TRANSPARENT);
804             paint.setShadowLayer(blur, 0, KEY_SHADOW_OFFSET_FACTOR * size, KEY_SHADOW_ALPHA << 24);
805             canvas.drawCircle(center, center, radius, paint);
806 
807             // Draw ambient shadow
808             paint.setShadowLayer(blur, 0, 0, AMBIENT_SHADOW_ALPHA << 24);
809             canvas.drawCircle(center, center, radius, paint);
810             paint.clearShadowLayer();
811         }
812 
813         // Draw the clipped icon
814         paint.setColor(Color.BLACK);
815         BitmapShader shader = new BitmapShader(adaptiveIconBitmap, Shader.TileMode.CLAMP,
816                 Shader.TileMode.CLAMP);
817         Matrix shift = new Matrix();
818         shift.setTranslate(-(adaptiveIconBitmap.getWidth() - size) / 2,
819                 -(adaptiveIconBitmap.getHeight() - size) / 2);
820         shader.setLocalMatrix(shift);
821         paint.setShader(shader);
822         canvas.drawCircle(center, center, radius, paint);
823 
824         canvas.setBitmap(null);
825         return icon;
826     }
827 }
828