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