• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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 android.annotation.ColorInt;
20 import android.annotation.DrawableRes;
21 import android.content.res.ColorStateList;
22 import android.content.ContentResolver;
23 import android.content.Context;
24 import android.content.pm.ApplicationInfo;
25 import android.content.pm.PackageManager;
26 import android.content.res.Resources;
27 import android.graphics.Bitmap;
28 import android.graphics.BitmapFactory;
29 import android.graphics.PorterDuff;
30 import android.net.Uri;
31 import android.os.AsyncTask;
32 import android.os.Handler;
33 import android.os.Message;
34 import android.os.Parcel;
35 import android.os.Parcelable;
36 import android.text.TextUtils;
37 import android.util.Log;
38 
39 import java.io.DataInputStream;
40 import java.io.DataOutputStream;
41 import java.io.File;
42 import java.io.FileInputStream;
43 import java.io.FileNotFoundException;
44 import java.io.IOException;
45 import java.io.InputStream;
46 import java.io.OutputStream;
47 
48 /**
49  * An umbrella container for several serializable graphics representations, including Bitmaps,
50  * compressed bitmap images (e.g. JPG or PNG), and drawable resources (including vectors).
51  *
52  * <a href="https://developer.android.com/training/displaying-bitmaps/index.html">Much ink</a>
53  * has been spilled on the best way to load images, and many clients may have different needs when
54  * it comes to threading and fetching. This class is therefore focused on encapsulation rather than
55  * behavior.
56  */
57 
58 public final class Icon implements Parcelable {
59     private static final String TAG = "Icon";
60 
61     /** @hide */
62     public static final int TYPE_BITMAP   = 1;
63     /** @hide */
64     public static final int TYPE_RESOURCE = 2;
65     /** @hide */
66     public static final int TYPE_DATA     = 3;
67     /** @hide */
68     public static final int TYPE_URI      = 4;
69 
70     private static final int VERSION_STREAM_SERIALIZER = 1;
71 
72     private final int mType;
73 
74     private ColorStateList mTintList;
75     static final PorterDuff.Mode DEFAULT_TINT_MODE = Drawable.DEFAULT_TINT_MODE; // SRC_IN
76     private PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE;
77 
78     // To avoid adding unnecessary overhead, we have a few basic objects that get repurposed
79     // based on the value of mType.
80 
81     // TYPE_BITMAP: Bitmap
82     // TYPE_RESOURCE: Resources
83     // TYPE_DATA: DataBytes
84     private Object          mObj1;
85 
86     // TYPE_RESOURCE: package name
87     // TYPE_URI: uri string
88     private String          mString1;
89 
90     // TYPE_RESOURCE: resId
91     // TYPE_DATA: data length
92     private int             mInt1;
93 
94     // TYPE_DATA: data offset
95     private int             mInt2;
96 
97     /**
98      * @return The type of image data held in this Icon. One of
99      * {@link #TYPE_BITMAP},
100      * {@link #TYPE_RESOURCE},
101      * {@link #TYPE_DATA}, or
102      * {@link #TYPE_URI}.
103      * @hide
104      */
getType()105     public int getType() {
106         return mType;
107     }
108 
109     /**
110      * @return The {@link android.graphics.Bitmap} held by this {@link #TYPE_BITMAP} Icon.
111      * @hide
112      */
getBitmap()113     public Bitmap getBitmap() {
114         if (mType != TYPE_BITMAP) {
115             throw new IllegalStateException("called getBitmap() on " + this);
116         }
117         return (Bitmap) mObj1;
118     }
119 
setBitmap(Bitmap b)120     private void setBitmap(Bitmap b) {
121         mObj1 = b;
122     }
123 
124     /**
125      * @return The length of the compressed bitmap byte array held by this {@link #TYPE_DATA} Icon.
126      * @hide
127      */
getDataLength()128     public int getDataLength() {
129         if (mType != TYPE_DATA) {
130             throw new IllegalStateException("called getDataLength() on " + this);
131         }
132         synchronized (this) {
133             return mInt1;
134         }
135     }
136 
137     /**
138      * @return The offset into the byte array held by this {@link #TYPE_DATA} Icon at which
139      * valid compressed bitmap data is found.
140      * @hide
141      */
getDataOffset()142     public int getDataOffset() {
143         if (mType != TYPE_DATA) {
144             throw new IllegalStateException("called getDataOffset() on " + this);
145         }
146         synchronized (this) {
147             return mInt2;
148         }
149     }
150 
151     /**
152      * @return The byte array held by this {@link #TYPE_DATA} Icon ctonaining compressed
153      * bitmap data.
154      * @hide
155      */
getDataBytes()156     public byte[] getDataBytes() {
157         if (mType != TYPE_DATA) {
158             throw new IllegalStateException("called getDataBytes() on " + this);
159         }
160         synchronized (this) {
161             return (byte[]) mObj1;
162         }
163     }
164 
165     /**
166      * @return The {@link android.content.res.Resources} for this {@link #TYPE_RESOURCE} Icon.
167      * @hide
168      */
getResources()169     public Resources getResources() {
170         if (mType != TYPE_RESOURCE) {
171             throw new IllegalStateException("called getResources() on " + this);
172         }
173         return (Resources) mObj1;
174     }
175 
176     /**
177      * @return The package containing resources for this {@link #TYPE_RESOURCE} Icon.
178      * @hide
179      */
getResPackage()180     public String getResPackage() {
181         if (mType != TYPE_RESOURCE) {
182             throw new IllegalStateException("called getResPackage() on " + this);
183         }
184         return mString1;
185     }
186 
187     /**
188      * @return The resource ID for this {@link #TYPE_RESOURCE} Icon.
189      * @hide
190      */
getResId()191     public int getResId() {
192         if (mType != TYPE_RESOURCE) {
193             throw new IllegalStateException("called getResId() on " + this);
194         }
195         return mInt1;
196     }
197 
198     /**
199      * @return The URI (as a String) for this {@link #TYPE_URI} Icon.
200      * @hide
201      */
getUriString()202     public String getUriString() {
203         if (mType != TYPE_URI) {
204             throw new IllegalStateException("called getUriString() on " + this);
205         }
206         return mString1;
207     }
208 
209     /**
210      * @return The {@link android.net.Uri} for this {@link #TYPE_URI} Icon.
211      * @hide
212      */
getUri()213     public Uri getUri() {
214         return Uri.parse(getUriString());
215     }
216 
typeToString(int x)217     private static final String typeToString(int x) {
218         switch (x) {
219             case TYPE_BITMAP: return "BITMAP";
220             case TYPE_DATA: return "DATA";
221             case TYPE_RESOURCE: return "RESOURCE";
222             case TYPE_URI: return "URI";
223             default: return "UNKNOWN";
224         }
225     }
226 
227     /**
228      * Invokes {@link #loadDrawable(Context)} on the given {@link android.os.Handler Handler}
229      * and then sends <code>andThen</code> to the same Handler when finished.
230      *
231      * @param context {@link android.content.Context Context} in which to load the drawable; see
232      *                {@link #loadDrawable(Context)}
233      * @param andThen {@link android.os.Message} to send to its target once the drawable
234      *                is available. The {@link android.os.Message#obj obj}
235      *                property is populated with the Drawable.
236      */
loadDrawableAsync(Context context, Message andThen)237     public void loadDrawableAsync(Context context, Message andThen) {
238         if (andThen.getTarget() == null) {
239             throw new IllegalArgumentException("callback message must have a target handler");
240         }
241         new LoadDrawableTask(context, andThen).runAsync();
242     }
243 
244     /**
245      * Invokes {@link #loadDrawable(Context)} on a background thread
246      * and then runs <code>andThen</code> on the UI thread when finished.
247      *
248      * @param context {@link Context Context} in which to load the drawable; see
249      *                {@link #loadDrawable(Context)}
250      * @param listener a callback to run on the provided
251      * @param handler {@link Handler} on which to run <code>andThen</code>.
252      */
loadDrawableAsync(Context context, final OnDrawableLoadedListener listener, Handler handler)253     public void loadDrawableAsync(Context context, final OnDrawableLoadedListener listener,
254             Handler handler) {
255         new LoadDrawableTask(context, handler, listener).runAsync();
256     }
257 
258     /**
259      * Returns a Drawable that can be used to draw the image inside this Icon, constructing it
260      * if necessary. Depending on the type of image, this may not be something you want to do on
261      * the UI thread, so consider using
262      * {@link #loadDrawableAsync(Context, Message) loadDrawableAsync} instead.
263      *
264      * @param context {@link android.content.Context Context} in which to load the drawable; used
265      *                to access {@link android.content.res.Resources Resources}, for example.
266      * @return A fresh instance of a drawable for this image, yours to keep.
267      */
loadDrawable(Context context)268     public Drawable loadDrawable(Context context) {
269         final Drawable result = loadDrawableInner(context);
270         if (result != null && (mTintList != null || mTintMode != DEFAULT_TINT_MODE)) {
271             result.mutate();
272             result.setTintList(mTintList);
273             result.setTintMode(mTintMode);
274         }
275         return result;
276     }
277 
278     /**
279      * Do the heavy lifting of loading the drawable, but stop short of applying any tint.
280      */
loadDrawableInner(Context context)281     private Drawable loadDrawableInner(Context context) {
282         switch (mType) {
283             case TYPE_BITMAP:
284                 return new BitmapDrawable(context.getResources(), getBitmap());
285             case TYPE_RESOURCE:
286                 if (getResources() == null) {
287                     // figure out where to load resources from
288                     String resPackage = getResPackage();
289                     if (TextUtils.isEmpty(resPackage)) {
290                         // if none is specified, try the given context
291                         resPackage = context.getPackageName();
292                     }
293                     if ("android".equals(resPackage)) {
294                         mObj1 = Resources.getSystem();
295                     } else {
296                         final PackageManager pm = context.getPackageManager();
297                         try {
298                             ApplicationInfo ai = pm.getApplicationInfo(
299                                     resPackage, PackageManager.GET_UNINSTALLED_PACKAGES);
300                             if (ai != null) {
301                                 mObj1 = pm.getResourcesForApplication(ai);
302                             } else {
303                                 break;
304                             }
305                         } catch (PackageManager.NameNotFoundException e) {
306                             Log.e(TAG, String.format("Unable to find pkg=%s for icon %s",
307                                     resPackage, this), e);
308                             break;
309                         }
310                     }
311                 }
312                 try {
313                     return getResources().getDrawable(getResId(), context.getTheme());
314                 } catch (RuntimeException e) {
315                     Log.e(TAG, String.format("Unable to load resource 0x%08x from pkg=%s",
316                                     getResId(),
317                                     getResPackage()),
318                             e);
319                 }
320                 break;
321             case TYPE_DATA:
322                 return new BitmapDrawable(context.getResources(),
323                     BitmapFactory.decodeByteArray(getDataBytes(), getDataOffset(), getDataLength())
324                 );
325             case TYPE_URI:
326                 final Uri uri = getUri();
327                 final String scheme = uri.getScheme();
328                 InputStream is = null;
329                 if (ContentResolver.SCHEME_CONTENT.equals(scheme)
330                         || ContentResolver.SCHEME_FILE.equals(scheme)) {
331                     try {
332                         is = context.getContentResolver().openInputStream(uri);
333                     } catch (Exception e) {
334                         Log.w(TAG, "Unable to load image from URI: " + uri, e);
335                     }
336                 } else {
337                     try {
338                         is = new FileInputStream(new File(mString1));
339                     } catch (FileNotFoundException e) {
340                         Log.w(TAG, "Unable to load image from path: " + uri, e);
341                     }
342                 }
343                 if (is != null) {
344                     return new BitmapDrawable(context.getResources(),
345                             BitmapFactory.decodeStream(is));
346                 }
347                 break;
348         }
349         return null;
350     }
351 
352     /**
353      * Load the requested resources under the given userId, if the system allows it,
354      * before actually loading the drawable.
355      *
356      * @hide
357      */
loadDrawableAsUser(Context context, int userId)358     public Drawable loadDrawableAsUser(Context context, int userId) {
359         if (mType == TYPE_RESOURCE) {
360             String resPackage = getResPackage();
361             if (TextUtils.isEmpty(resPackage)) {
362                 resPackage = context.getPackageName();
363             }
364             if (getResources() == null && !(getResPackage().equals("android"))) {
365                 final PackageManager pm = context.getPackageManager();
366                 try {
367                     // assign getResources() as the correct user
368                     mObj1 = pm.getResourcesForApplicationAsUser(resPackage, userId);
369                 } catch (PackageManager.NameNotFoundException e) {
370                     Log.e(TAG, String.format("Unable to find pkg=%s user=%d",
371                                     getResPackage(),
372                                     userId),
373                             e);
374                 }
375             }
376         }
377         return loadDrawable(context);
378     }
379 
380     /** @hide */
381     public static final int MIN_ASHMEM_ICON_SIZE = 128 * (1 << 10);
382 
383     /**
384      * Puts the memory used by this instance into Ashmem memory, if possible.
385      * @hide
386      */
convertToAshmem()387     public void convertToAshmem() {
388         if (mType == TYPE_BITMAP &&
389             getBitmap().isMutable() &&
390             getBitmap().getAllocationByteCount() >= MIN_ASHMEM_ICON_SIZE) {
391             setBitmap(getBitmap().createAshmemBitmap());
392         }
393     }
394 
395     /**
396      * Writes a serialized version of an Icon to the specified stream.
397      *
398      * @param stream The stream on which to serialize the Icon.
399      * @hide
400      */
writeToStream(OutputStream stream)401     public void writeToStream(OutputStream stream) throws IOException {
402         DataOutputStream dataStream = new DataOutputStream(stream);
403 
404         dataStream.writeInt(VERSION_STREAM_SERIALIZER);
405         dataStream.writeByte(mType);
406 
407         switch (mType) {
408             case TYPE_BITMAP:
409                 getBitmap().compress(Bitmap.CompressFormat.PNG, 100, dataStream);
410                 break;
411             case TYPE_DATA:
412                 dataStream.writeInt(getDataLength());
413                 dataStream.write(getDataBytes(), getDataOffset(), getDataLength());
414                 break;
415             case TYPE_RESOURCE:
416                 dataStream.writeUTF(getResPackage());
417                 dataStream.writeInt(getResId());
418                 break;
419             case TYPE_URI:
420                 dataStream.writeUTF(getUriString());
421                 break;
422         }
423     }
424 
Icon(int mType)425     private Icon(int mType) {
426         this.mType = mType;
427     }
428 
429     /**
430      * Create an Icon from the specified stream.
431      *
432      * @param stream The input stream from which to reconstruct the Icon.
433      * @hide
434      */
createFromStream(InputStream stream)435     public static Icon createFromStream(InputStream stream) throws IOException {
436         DataInputStream inputStream = new DataInputStream(stream);
437 
438         final int version = inputStream.readInt();
439         if (version >= VERSION_STREAM_SERIALIZER) {
440             final int type = inputStream.readByte();
441             switch (type) {
442                 case TYPE_BITMAP:
443                     return createWithBitmap(BitmapFactory.decodeStream(inputStream));
444                 case TYPE_DATA:
445                     final int length = inputStream.readInt();
446                     final byte[] data = new byte[length];
447                     inputStream.read(data, 0 /* offset */, length);
448                     return createWithData(data, 0 /* offset */, length);
449                 case TYPE_RESOURCE:
450                     final String packageName = inputStream.readUTF();
451                     final int resId = inputStream.readInt();
452                     return createWithResource(packageName, resId);
453                 case TYPE_URI:
454                     final String uriOrPath = inputStream.readUTF();
455                     return createWithContentUri(uriOrPath);
456             }
457         }
458         return null;
459     }
460 
461     /**
462      * Create an Icon pointing to a drawable resource.
463      * @param context The context for the application whose resources should be used to resolve the
464      *                given resource ID.
465      * @param resId ID of the drawable resource
466      */
createWithResource(Context context, @DrawableRes int resId)467     public static Icon createWithResource(Context context, @DrawableRes int resId) {
468         if (context == null) {
469             throw new IllegalArgumentException("Context must not be null.");
470         }
471         final Icon rep = new Icon(TYPE_RESOURCE);
472         rep.mInt1 = resId;
473         rep.mString1 = context.getPackageName();
474         return rep;
475     }
476 
477     /**
478      * Version of createWithResource that takes Resources. Do not use.
479      * @hide
480      */
createWithResource(Resources res, @DrawableRes int resId)481     public static Icon createWithResource(Resources res, @DrawableRes int resId) {
482         if (res == null) {
483             throw new IllegalArgumentException("Resource must not be null.");
484         }
485         final Icon rep = new Icon(TYPE_RESOURCE);
486         rep.mInt1 = resId;
487         rep.mString1 = res.getResourcePackageName(resId);
488         return rep;
489     }
490 
491     /**
492      * Create an Icon pointing to a drawable resource.
493      * @param resPackage Name of the package containing the resource in question
494      * @param resId ID of the drawable resource
495      */
createWithResource(String resPackage, @DrawableRes int resId)496     public static Icon createWithResource(String resPackage, @DrawableRes int resId) {
497         if (resPackage == null) {
498             throw new IllegalArgumentException("Resource package name must not be null.");
499         }
500         final Icon rep = new Icon(TYPE_RESOURCE);
501         rep.mInt1 = resId;
502         rep.mString1 = resPackage;
503         return rep;
504     }
505 
506     /**
507      * Create an Icon pointing to a bitmap in memory.
508      * @param bits A valid {@link android.graphics.Bitmap} object
509      */
createWithBitmap(Bitmap bits)510     public static Icon createWithBitmap(Bitmap bits) {
511         if (bits == null) {
512             throw new IllegalArgumentException("Bitmap must not be null.");
513         }
514         final Icon rep = new Icon(TYPE_BITMAP);
515         rep.setBitmap(bits);
516         return rep;
517     }
518 
519     /**
520      * Create an Icon pointing to a compressed bitmap stored in a byte array.
521      * @param data Byte array storing compressed bitmap data of a type that
522      *             {@link android.graphics.BitmapFactory}
523      *             can decode (see {@link android.graphics.Bitmap.CompressFormat}).
524      * @param offset Offset into <code>data</code> at which the bitmap data starts
525      * @param length Length of the bitmap data
526      */
createWithData(byte[] data, int offset, int length)527     public static Icon createWithData(byte[] data, int offset, int length) {
528         if (data == null) {
529             throw new IllegalArgumentException("Data must not be null.");
530         }
531         final Icon rep = new Icon(TYPE_DATA);
532         rep.mObj1 = data;
533         rep.mInt1 = length;
534         rep.mInt2 = offset;
535         return rep;
536     }
537 
538     /**
539      * Create an Icon pointing to an image file specified by URI.
540      *
541      * @param uri A uri referring to local content:// or file:// image data.
542      */
createWithContentUri(String uri)543     public static Icon createWithContentUri(String uri) {
544         if (uri == null) {
545             throw new IllegalArgumentException("Uri must not be null.");
546         }
547         final Icon rep = new Icon(TYPE_URI);
548         rep.mString1 = uri;
549         return rep;
550     }
551 
552     /**
553      * Create an Icon pointing to an image file specified by URI.
554      *
555      * @param uri A uri referring to local content:// or file:// image data.
556      */
createWithContentUri(Uri uri)557     public static Icon createWithContentUri(Uri uri) {
558         if (uri == null) {
559             throw new IllegalArgumentException("Uri must not be null.");
560         }
561         final Icon rep = new Icon(TYPE_URI);
562         rep.mString1 = uri.toString();
563         return rep;
564     }
565 
566     /**
567      * Store a color to use whenever this Icon is drawn.
568      *
569      * @param tint a color, as in {@link Drawable#setTint(int)}
570      * @return this same object, for use in chained construction
571      */
setTint(@olorInt int tint)572     public Icon setTint(@ColorInt int tint) {
573         return setTintList(ColorStateList.valueOf(tint));
574     }
575 
576     /**
577      * Store a color to use whenever this Icon is drawn.
578      *
579      * @param tintList as in {@link Drawable#setTintList(ColorStateList)}, null to remove tint
580      * @return this same object, for use in chained construction
581      */
setTintList(ColorStateList tintList)582     public Icon setTintList(ColorStateList tintList) {
583         mTintList = tintList;
584         return this;
585     }
586 
587     /**
588      * Store a blending mode to use whenever this Icon is drawn.
589      *
590      * @param mode a blending mode, as in {@link Drawable#setTintMode(PorterDuff.Mode)}, may be null
591      * @return this same object, for use in chained construction
592      */
setTintMode(PorterDuff.Mode mode)593     public Icon setTintMode(PorterDuff.Mode mode) {
594         mTintMode = mode;
595         return this;
596     }
597 
598     /**
599      * Create an Icon pointing to an image file specified by path.
600      *
601      * @param path A path to a file that contains compressed bitmap data of
602      *           a type that {@link android.graphics.BitmapFactory} can decode.
603      */
createWithFilePath(String path)604     public static Icon createWithFilePath(String path) {
605         if (path == null) {
606             throw new IllegalArgumentException("Path must not be null.");
607         }
608         final Icon rep = new Icon(TYPE_URI);
609         rep.mString1 = path;
610         return rep;
611     }
612 
613     @Override
toString()614     public String toString() {
615         final StringBuilder sb = new StringBuilder("Icon(typ=").append(typeToString(mType));
616         switch (mType) {
617             case TYPE_BITMAP:
618                 sb.append(" size=")
619                         .append(getBitmap().getWidth())
620                         .append("x")
621                         .append(getBitmap().getHeight());
622                 break;
623             case TYPE_RESOURCE:
624                 sb.append(" pkg=")
625                         .append(getResPackage())
626                         .append(" id=")
627                         .append(String.format("0x%08x", getResId()));
628                 break;
629             case TYPE_DATA:
630                 sb.append(" len=").append(getDataLength());
631                 if (getDataOffset() != 0) {
632                     sb.append(" off=").append(getDataOffset());
633                 }
634                 break;
635             case TYPE_URI:
636                 sb.append(" uri=").append(getUriString());
637                 break;
638         }
639         if (mTintList != null) {
640             sb.append(" tint=");
641             String sep = "";
642             for (int c : mTintList.getColors()) {
643                 sb.append(String.format("%s0x%08x", sep, c));
644                 sep = "|";
645             }
646         }
647         if (mTintMode != DEFAULT_TINT_MODE) sb.append(" mode=").append(mTintMode);
648         sb.append(")");
649         return sb.toString();
650     }
651 
652     /**
653      * Parcelable interface
654      */
describeContents()655     public int describeContents() {
656         return (mType == TYPE_BITMAP || mType == TYPE_DATA)
657                 ? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0;
658     }
659 
660     // ===== Parcelable interface ======
661 
Icon(Parcel in)662     private Icon(Parcel in) {
663         this(in.readInt());
664         switch (mType) {
665             case TYPE_BITMAP:
666                 final Bitmap bits = Bitmap.CREATOR.createFromParcel(in);
667                 mObj1 = bits;
668                 break;
669             case TYPE_RESOURCE:
670                 final String pkg = in.readString();
671                 final int resId = in.readInt();
672                 mString1 = pkg;
673                 mInt1 = resId;
674                 break;
675             case TYPE_DATA:
676                 final int len = in.readInt();
677                 final byte[] a = in.readBlob();
678                 if (len != a.length) {
679                     throw new RuntimeException("internal unparceling error: blob length ("
680                             + a.length + ") != expected length (" + len + ")");
681                 }
682                 mInt1 = len;
683                 mObj1 = a;
684                 break;
685             case TYPE_URI:
686                 final String uri = in.readString();
687                 mString1 = uri;
688                 break;
689             default:
690                 throw new RuntimeException("invalid "
691                         + this.getClass().getSimpleName() + " type in parcel: " + mType);
692         }
693         if (in.readInt() == 1) {
694             mTintList = ColorStateList.CREATOR.createFromParcel(in);
695         }
696         mTintMode = PorterDuff.intToMode(in.readInt());
697     }
698 
699     @Override
writeToParcel(Parcel dest, int flags)700     public void writeToParcel(Parcel dest, int flags) {
701         dest.writeInt(mType);
702         switch (mType) {
703             case TYPE_BITMAP:
704                 final Bitmap bits = getBitmap();
705                 getBitmap().writeToParcel(dest, flags);
706                 break;
707             case TYPE_RESOURCE:
708                 dest.writeString(getResPackage());
709                 dest.writeInt(getResId());
710                 break;
711             case TYPE_DATA:
712                 dest.writeInt(getDataLength());
713                 dest.writeBlob(getDataBytes(), getDataOffset(), getDataLength());
714                 break;
715             case TYPE_URI:
716                 dest.writeString(getUriString());
717                 break;
718         }
719         if (mTintList == null) {
720             dest.writeInt(0);
721         } else {
722             dest.writeInt(1);
723             mTintList.writeToParcel(dest, flags);
724         }
725         dest.writeInt(PorterDuff.modeToInt(mTintMode));
726     }
727 
728     public static final Parcelable.Creator<Icon> CREATOR
729             = new Parcelable.Creator<Icon>() {
730         public Icon createFromParcel(Parcel in) {
731             return new Icon(in);
732         }
733 
734         public Icon[] newArray(int size) {
735             return new Icon[size];
736         }
737     };
738 
739     /**
740      * Implement this interface to receive a callback when
741      * {@link #loadDrawableAsync(Context, OnDrawableLoadedListener, Handler) loadDrawableAsync}
742      * is finished and your Drawable is ready.
743      */
744     public interface OnDrawableLoadedListener {
onDrawableLoaded(Drawable d)745         void onDrawableLoaded(Drawable d);
746     }
747 
748     /**
749      * Wrapper around loadDrawable that does its work on a pooled thread and then
750      * fires back the given (targeted) Message.
751      */
752     private class LoadDrawableTask implements Runnable {
753         final Context mContext;
754         final Message mMessage;
755 
LoadDrawableTask(Context context, final Handler handler, final OnDrawableLoadedListener listener)756         public LoadDrawableTask(Context context, final Handler handler,
757                 final OnDrawableLoadedListener listener) {
758             mContext = context;
759             mMessage = Message.obtain(handler, new Runnable() {
760                     @Override
761                     public void run() {
762                         listener.onDrawableLoaded((Drawable) mMessage.obj);
763                     }
764                 });
765         }
766 
LoadDrawableTask(Context context, Message message)767         public LoadDrawableTask(Context context, Message message) {
768             mContext = context;
769             mMessage = message;
770         }
771 
772         @Override
run()773         public void run() {
774             mMessage.obj = loadDrawable(mContext);
775             mMessage.sendToTarget();
776         }
777 
runAsync()778         public void runAsync() {
779             AsyncTask.THREAD_POOL_EXECUTOR.execute(this);
780         }
781     }
782 }
783