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