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