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