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