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