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     /**
381      * Puts the memory used by this instance into Ashmem memory, if possible.
382      * @hide
383      */
convertToAshmem()384     public void convertToAshmem() {
385         if (mType == TYPE_BITMAP && getBitmap().isMutable()) {
386             setBitmap(getBitmap().createAshmemBitmap());
387         }
388     }
389 
390     /**
391      * Writes a serialized version of an Icon to the specified stream.
392      *
393      * @param stream The stream on which to serialize the Icon.
394      * @hide
395      */
writeToStream(OutputStream stream)396     public void writeToStream(OutputStream stream) throws IOException {
397         DataOutputStream dataStream = new DataOutputStream(stream);
398 
399         dataStream.writeInt(VERSION_STREAM_SERIALIZER);
400         dataStream.writeByte(mType);
401 
402         switch (mType) {
403             case TYPE_BITMAP:
404                 getBitmap().compress(Bitmap.CompressFormat.PNG, 100, dataStream);
405                 break;
406             case TYPE_DATA:
407                 dataStream.writeInt(getDataLength());
408                 dataStream.write(getDataBytes(), getDataOffset(), getDataLength());
409                 break;
410             case TYPE_RESOURCE:
411                 dataStream.writeUTF(getResPackage());
412                 dataStream.writeInt(getResId());
413                 break;
414             case TYPE_URI:
415                 dataStream.writeUTF(getUriString());
416                 break;
417         }
418     }
419 
Icon(int mType)420     private Icon(int mType) {
421         this.mType = mType;
422     }
423 
424     /**
425      * Create an Icon from the specified stream.
426      *
427      * @param stream The input stream from which to reconstruct the Icon.
428      * @hide
429      */
createFromStream(InputStream stream)430     public static Icon createFromStream(InputStream stream) throws IOException {
431         DataInputStream inputStream = new DataInputStream(stream);
432 
433         final int version = inputStream.readInt();
434         if (version >= VERSION_STREAM_SERIALIZER) {
435             final int type = inputStream.readByte();
436             switch (type) {
437                 case TYPE_BITMAP:
438                     return createWithBitmap(BitmapFactory.decodeStream(inputStream));
439                 case TYPE_DATA:
440                     final int length = inputStream.readInt();
441                     final byte[] data = new byte[length];
442                     inputStream.read(data, 0 /* offset */, length);
443                     return createWithData(data, 0 /* offset */, length);
444                 case TYPE_RESOURCE:
445                     final String packageName = inputStream.readUTF();
446                     final int resId = inputStream.readInt();
447                     return createWithResource(packageName, resId);
448                 case TYPE_URI:
449                     final String uriOrPath = inputStream.readUTF();
450                     return createWithContentUri(uriOrPath);
451             }
452         }
453         return null;
454     }
455 
456     /**
457      * Create an Icon pointing to a drawable resource.
458      * @param context The context for the application whose resources should be used to resolve the
459      *                given resource ID.
460      * @param resId ID of the drawable resource
461      */
createWithResource(Context context, @DrawableRes int resId)462     public static Icon createWithResource(Context context, @DrawableRes int resId) {
463         if (context == null) {
464             throw new IllegalArgumentException("Context must not be null.");
465         }
466         final Icon rep = new Icon(TYPE_RESOURCE);
467         rep.mInt1 = resId;
468         rep.mString1 = context.getPackageName();
469         return rep;
470     }
471 
472     /**
473      * Version of createWithResource that takes Resources. Do not use.
474      * @hide
475      */
createWithResource(Resources res, @DrawableRes int resId)476     public static Icon createWithResource(Resources res, @DrawableRes int resId) {
477         if (res == null) {
478             throw new IllegalArgumentException("Resource must not be null.");
479         }
480         final Icon rep = new Icon(TYPE_RESOURCE);
481         rep.mInt1 = resId;
482         rep.mString1 = res.getResourcePackageName(resId);
483         return rep;
484     }
485 
486     /**
487      * Create an Icon pointing to a drawable resource.
488      * @param resPackage Name of the package containing the resource in question
489      * @param resId ID of the drawable resource
490      */
createWithResource(String resPackage, @DrawableRes int resId)491     public static Icon createWithResource(String resPackage, @DrawableRes int resId) {
492         if (resPackage == null) {
493             throw new IllegalArgumentException("Resource package name must not be null.");
494         }
495         final Icon rep = new Icon(TYPE_RESOURCE);
496         rep.mInt1 = resId;
497         rep.mString1 = resPackage;
498         return rep;
499     }
500 
501     /**
502      * Create an Icon pointing to a bitmap in memory.
503      * @param bits A valid {@link android.graphics.Bitmap} object
504      */
createWithBitmap(Bitmap bits)505     public static Icon createWithBitmap(Bitmap bits) {
506         if (bits == null) {
507             throw new IllegalArgumentException("Bitmap must not be null.");
508         }
509         final Icon rep = new Icon(TYPE_BITMAP);
510         rep.setBitmap(bits);
511         return rep;
512     }
513 
514     /**
515      * Create an Icon pointing to a compressed bitmap stored in a byte array.
516      * @param data Byte array storing compressed bitmap data of a type that
517      *             {@link android.graphics.BitmapFactory}
518      *             can decode (see {@link android.graphics.Bitmap.CompressFormat}).
519      * @param offset Offset into <code>data</code> at which the bitmap data starts
520      * @param length Length of the bitmap data
521      */
createWithData(byte[] data, int offset, int length)522     public static Icon createWithData(byte[] data, int offset, int length) {
523         if (data == null) {
524             throw new IllegalArgumentException("Data must not be null.");
525         }
526         final Icon rep = new Icon(TYPE_DATA);
527         rep.mObj1 = data;
528         rep.mInt1 = length;
529         rep.mInt2 = offset;
530         return rep;
531     }
532 
533     /**
534      * Create an Icon pointing to an image file specified by URI.
535      *
536      * @param uri A uri referring to local content:// or file:// image data.
537      */
createWithContentUri(String uri)538     public static Icon createWithContentUri(String uri) {
539         if (uri == null) {
540             throw new IllegalArgumentException("Uri must not be null.");
541         }
542         final Icon rep = new Icon(TYPE_URI);
543         rep.mString1 = uri;
544         return rep;
545     }
546 
547     /**
548      * Create an Icon pointing to an image file specified by URI.
549      *
550      * @param uri A uri referring to local content:// or file:// image data.
551      */
createWithContentUri(Uri uri)552     public static Icon createWithContentUri(Uri uri) {
553         if (uri == null) {
554             throw new IllegalArgumentException("Uri must not be null.");
555         }
556         final Icon rep = new Icon(TYPE_URI);
557         rep.mString1 = uri.toString();
558         return rep;
559     }
560 
561     /**
562      * Store a color to use whenever this Icon is drawn.
563      *
564      * @param tint a color, as in {@link Drawable#setTint(int)}
565      * @return this same object, for use in chained construction
566      */
setTint(@olorInt int tint)567     public Icon setTint(@ColorInt int tint) {
568         return setTintList(ColorStateList.valueOf(tint));
569     }
570 
571     /**
572      * Store a color to use whenever this Icon is drawn.
573      *
574      * @param tintList as in {@link Drawable#setTintList(ColorStateList)}, null to remove tint
575      * @return this same object, for use in chained construction
576      */
setTintList(ColorStateList tintList)577     public Icon setTintList(ColorStateList tintList) {
578         mTintList = tintList;
579         return this;
580     }
581 
582     /**
583      * Store a blending mode to use whenever this Icon is drawn.
584      *
585      * @param mode a blending mode, as in {@link Drawable#setTintMode(PorterDuff.Mode)}, may be null
586      * @return this same object, for use in chained construction
587      */
setTintMode(PorterDuff.Mode mode)588     public Icon setTintMode(PorterDuff.Mode mode) {
589         mTintMode = mode;
590         return this;
591     }
592 
593     /**
594      * Create an Icon pointing to an image file specified by path.
595      *
596      * @param path A path to a file that contains compressed bitmap data of
597      *           a type that {@link android.graphics.BitmapFactory} can decode.
598      */
createWithFilePath(String path)599     public static Icon createWithFilePath(String path) {
600         if (path == null) {
601             throw new IllegalArgumentException("Path must not be null.");
602         }
603         final Icon rep = new Icon(TYPE_URI);
604         rep.mString1 = path;
605         return rep;
606     }
607 
608     @Override
toString()609     public String toString() {
610         final StringBuilder sb = new StringBuilder("Icon(typ=").append(typeToString(mType));
611         switch (mType) {
612             case TYPE_BITMAP:
613                 sb.append(" size=")
614                         .append(getBitmap().getWidth())
615                         .append("x")
616                         .append(getBitmap().getHeight());
617                 break;
618             case TYPE_RESOURCE:
619                 sb.append(" pkg=")
620                         .append(getResPackage())
621                         .append(" id=")
622                         .append(String.format("0x%08x", getResId()));
623                 break;
624             case TYPE_DATA:
625                 sb.append(" len=").append(getDataLength());
626                 if (getDataOffset() != 0) {
627                     sb.append(" off=").append(getDataOffset());
628                 }
629                 break;
630             case TYPE_URI:
631                 sb.append(" uri=").append(getUriString());
632                 break;
633         }
634         if (mTintList != null) {
635             sb.append(" tint=");
636             String sep = "";
637             for (int c : mTintList.getColors()) {
638                 sb.append(String.format("%s0x%08x", sep, c));
639                 sep = "|";
640             }
641         }
642         if (mTintMode != DEFAULT_TINT_MODE) sb.append(" mode=").append(mTintMode);
643         sb.append(")");
644         return sb.toString();
645     }
646 
647     /**
648      * Parcelable interface
649      */
describeContents()650     public int describeContents() {
651         return (mType == TYPE_BITMAP || mType == TYPE_DATA)
652                 ? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0;
653     }
654 
655     // ===== Parcelable interface ======
656 
Icon(Parcel in)657     private Icon(Parcel in) {
658         this(in.readInt());
659         switch (mType) {
660             case TYPE_BITMAP:
661                 final Bitmap bits = Bitmap.CREATOR.createFromParcel(in);
662                 mObj1 = bits;
663                 break;
664             case TYPE_RESOURCE:
665                 final String pkg = in.readString();
666                 final int resId = in.readInt();
667                 mString1 = pkg;
668                 mInt1 = resId;
669                 break;
670             case TYPE_DATA:
671                 final int len = in.readInt();
672                 final byte[] a = in.readBlob();
673                 if (len != a.length) {
674                     throw new RuntimeException("internal unparceling error: blob length ("
675                             + a.length + ") != expected length (" + len + ")");
676                 }
677                 mInt1 = len;
678                 mObj1 = a;
679                 break;
680             case TYPE_URI:
681                 final String uri = in.readString();
682                 mString1 = uri;
683                 break;
684             default:
685                 throw new RuntimeException("invalid "
686                         + this.getClass().getSimpleName() + " type in parcel: " + mType);
687         }
688         if (in.readInt() == 1) {
689             mTintList = ColorStateList.CREATOR.createFromParcel(in);
690         }
691         mTintMode = PorterDuff.intToMode(in.readInt());
692     }
693 
694     @Override
writeToParcel(Parcel dest, int flags)695     public void writeToParcel(Parcel dest, int flags) {
696         dest.writeInt(mType);
697         switch (mType) {
698             case TYPE_BITMAP:
699                 final Bitmap bits = getBitmap();
700                 getBitmap().writeToParcel(dest, flags);
701                 break;
702             case TYPE_RESOURCE:
703                 dest.writeString(getResPackage());
704                 dest.writeInt(getResId());
705                 break;
706             case TYPE_DATA:
707                 dest.writeInt(getDataLength());
708                 dest.writeBlob(getDataBytes(), getDataOffset(), getDataLength());
709                 break;
710             case TYPE_URI:
711                 dest.writeString(getUriString());
712                 break;
713         }
714         if (mTintList == null) {
715             dest.writeInt(0);
716         } else {
717             dest.writeInt(1);
718             mTintList.writeToParcel(dest, flags);
719         }
720         dest.writeInt(PorterDuff.modeToInt(mTintMode));
721     }
722 
723     public static final Parcelable.Creator<Icon> CREATOR
724             = new Parcelable.Creator<Icon>() {
725         public Icon createFromParcel(Parcel in) {
726             return new Icon(in);
727         }
728 
729         public Icon[] newArray(int size) {
730             return new Icon[size];
731         }
732     };
733 
734     /**
735      * Implement this interface to receive a callback when
736      * {@link #loadDrawableAsync(Context, OnDrawableLoadedListener, Handler) loadDrawableAsync}
737      * is finished and your Drawable is ready.
738      */
739     public interface OnDrawableLoadedListener {
onDrawableLoaded(Drawable d)740         void onDrawableLoaded(Drawable d);
741     }
742 
743     /**
744      * Wrapper around loadDrawable that does its work on a pooled thread and then
745      * fires back the given (targeted) Message.
746      */
747     private class LoadDrawableTask implements Runnable {
748         final Context mContext;
749         final Message mMessage;
750 
LoadDrawableTask(Context context, final Handler handler, final OnDrawableLoadedListener listener)751         public LoadDrawableTask(Context context, final Handler handler,
752                 final OnDrawableLoadedListener listener) {
753             mContext = context;
754             mMessage = Message.obtain(handler, new Runnable() {
755                     @Override
756                     public void run() {
757                         listener.onDrawableLoaded((Drawable) mMessage.obj);
758                     }
759                 });
760         }
761 
LoadDrawableTask(Context context, Message message)762         public LoadDrawableTask(Context context, Message message) {
763             mContext = context;
764             mMessage = message;
765         }
766 
767         @Override
run()768         public void run() {
769             mMessage.obj = loadDrawable(mContext);
770             mMessage.sendToTarget();
771         }
772 
runAsync()773         public void runAsync() {
774             AsyncTask.THREAD_POOL_EXECUTOR.execute(this);
775         }
776     }
777 }
778