Icon.java revision 0394a0c399582d4401646cf2324f3148c3028531
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 /** @hide */ 381 public static final int MIN_ASHMEM_ICON_SIZE = 128 * (1 << 10); 382 383 /** 384 * Puts the memory used by this instance into Ashmem memory, if possible. 385 * @hide 386 */ 387 public void convertToAshmem() { 388 if (mType == TYPE_BITMAP && 389 getBitmap().isMutable() && 390 getBitmap().getAllocationByteCount() >= MIN_ASHMEM_ICON_SIZE) { 391 setBitmap(getBitmap().createAshmemBitmap()); 392 } 393 } 394 395 /** 396 * Writes a serialized version of an Icon to the specified stream. 397 * 398 * @param stream The stream on which to serialize the Icon. 399 * @hide 400 */ 401 public void writeToStream(OutputStream stream) throws IOException { 402 DataOutputStream dataStream = new DataOutputStream(stream); 403 404 dataStream.writeInt(VERSION_STREAM_SERIALIZER); 405 dataStream.writeByte(mType); 406 407 switch (mType) { 408 case TYPE_BITMAP: 409 getBitmap().compress(Bitmap.CompressFormat.PNG, 100, dataStream); 410 break; 411 case TYPE_DATA: 412 dataStream.writeInt(getDataLength()); 413 dataStream.write(getDataBytes(), getDataOffset(), getDataLength()); 414 break; 415 case TYPE_RESOURCE: 416 dataStream.writeUTF(getResPackage()); 417 dataStream.writeInt(getResId()); 418 break; 419 case TYPE_URI: 420 dataStream.writeUTF(getUriString()); 421 break; 422 } 423 } 424 425 private Icon(int mType) { 426 this.mType = mType; 427 } 428 429 /** 430 * Create an Icon from the specified stream. 431 * 432 * @param stream The input stream from which to reconstruct the Icon. 433 * @hide 434 */ 435 public static Icon createFromStream(InputStream stream) throws IOException { 436 DataInputStream inputStream = new DataInputStream(stream); 437 438 final int version = inputStream.readInt(); 439 if (version >= VERSION_STREAM_SERIALIZER) { 440 final int type = inputStream.readByte(); 441 switch (type) { 442 case TYPE_BITMAP: 443 return createWithBitmap(BitmapFactory.decodeStream(inputStream)); 444 case TYPE_DATA: 445 final int length = inputStream.readInt(); 446 final byte[] data = new byte[length]; 447 inputStream.read(data, 0 /* offset */, length); 448 return createWithData(data, 0 /* offset */, length); 449 case TYPE_RESOURCE: 450 final String packageName = inputStream.readUTF(); 451 final int resId = inputStream.readInt(); 452 return createWithResource(packageName, resId); 453 case TYPE_URI: 454 final String uriOrPath = inputStream.readUTF(); 455 return createWithContentUri(uriOrPath); 456 } 457 } 458 return null; 459 } 460 461 /** 462 * Create an Icon pointing to a drawable resource. 463 * @param context The context for the application whose resources should be used to resolve the 464 * given resource ID. 465 * @param resId ID of the drawable resource 466 */ 467 public static Icon createWithResource(Context context, @DrawableRes int resId) { 468 if (context == null) { 469 throw new IllegalArgumentException("Context must not be null."); 470 } 471 final Icon rep = new Icon(TYPE_RESOURCE); 472 rep.mInt1 = resId; 473 rep.mString1 = context.getPackageName(); 474 return rep; 475 } 476 477 /** 478 * Version of createWithResource that takes Resources. Do not use. 479 * @hide 480 */ 481 public static Icon createWithResource(Resources res, @DrawableRes int resId) { 482 if (res == null) { 483 throw new IllegalArgumentException("Resource must not be null."); 484 } 485 final Icon rep = new Icon(TYPE_RESOURCE); 486 rep.mInt1 = resId; 487 rep.mString1 = res.getResourcePackageName(resId); 488 return rep; 489 } 490 491 /** 492 * Create an Icon pointing to a drawable resource. 493 * @param resPackage Name of the package containing the resource in question 494 * @param resId ID of the drawable resource 495 */ 496 public static Icon createWithResource(String resPackage, @DrawableRes int resId) { 497 if (resPackage == null) { 498 throw new IllegalArgumentException("Resource package name must not be null."); 499 } 500 final Icon rep = new Icon(TYPE_RESOURCE); 501 rep.mInt1 = resId; 502 rep.mString1 = resPackage; 503 return rep; 504 } 505 506 /** 507 * Create an Icon pointing to a bitmap in memory. 508 * @param bits A valid {@link android.graphics.Bitmap} object 509 */ 510 public static Icon createWithBitmap(Bitmap bits) { 511 if (bits == null) { 512 throw new IllegalArgumentException("Bitmap must not be null."); 513 } 514 final Icon rep = new Icon(TYPE_BITMAP); 515 rep.setBitmap(bits); 516 return rep; 517 } 518 519 /** 520 * Create an Icon pointing to a compressed bitmap stored in a byte array. 521 * @param data Byte array storing compressed bitmap data of a type that 522 * {@link android.graphics.BitmapFactory} 523 * can decode (see {@link android.graphics.Bitmap.CompressFormat}). 524 * @param offset Offset into <code>data</code> at which the bitmap data starts 525 * @param length Length of the bitmap data 526 */ 527 public static Icon createWithData(byte[] data, int offset, int length) { 528 if (data == null) { 529 throw new IllegalArgumentException("Data must not be null."); 530 } 531 final Icon rep = new Icon(TYPE_DATA); 532 rep.mObj1 = data; 533 rep.mInt1 = length; 534 rep.mInt2 = offset; 535 return rep; 536 } 537 538 /** 539 * Create an Icon pointing to an image file specified by URI. 540 * 541 * @param uri A uri referring to local content:// or file:// image data. 542 */ 543 public static Icon createWithContentUri(String uri) { 544 if (uri == null) { 545 throw new IllegalArgumentException("Uri must not be null."); 546 } 547 final Icon rep = new Icon(TYPE_URI); 548 rep.mString1 = uri; 549 return rep; 550 } 551 552 /** 553 * Create an Icon pointing to an image file specified by URI. 554 * 555 * @param uri A uri referring to local content:// or file:// image data. 556 */ 557 public static Icon createWithContentUri(Uri uri) { 558 if (uri == null) { 559 throw new IllegalArgumentException("Uri must not be null."); 560 } 561 final Icon rep = new Icon(TYPE_URI); 562 rep.mString1 = uri.toString(); 563 return rep; 564 } 565 566 /** 567 * Store a color to use whenever this Icon is drawn. 568 * 569 * @param tint a color, as in {@link Drawable#setTint(int)} 570 * @return this same object, for use in chained construction 571 */ 572 public Icon setTint(@ColorInt int tint) { 573 return setTintList(ColorStateList.valueOf(tint)); 574 } 575 576 /** 577 * Store a color to use whenever this Icon is drawn. 578 * 579 * @param tintList as in {@link Drawable#setTintList(ColorStateList)}, null to remove tint 580 * @return this same object, for use in chained construction 581 */ 582 public Icon setTintList(ColorStateList tintList) { 583 mTintList = tintList; 584 return this; 585 } 586 587 /** 588 * Store a blending mode to use whenever this Icon is drawn. 589 * 590 * @param mode a blending mode, as in {@link Drawable#setTintMode(PorterDuff.Mode)}, may be null 591 * @return this same object, for use in chained construction 592 */ 593 public Icon setTintMode(PorterDuff.Mode mode) { 594 mTintMode = mode; 595 return this; 596 } 597 598 /** 599 * Create an Icon pointing to an image file specified by path. 600 * 601 * @param path A path to a file that contains compressed bitmap data of 602 * a type that {@link android.graphics.BitmapFactory} can decode. 603 */ 604 public static Icon createWithFilePath(String path) { 605 if (path == null) { 606 throw new IllegalArgumentException("Path must not be null."); 607 } 608 final Icon rep = new Icon(TYPE_URI); 609 rep.mString1 = path; 610 return rep; 611 } 612 613 @Override 614 public String toString() { 615 final StringBuilder sb = new StringBuilder("Icon(typ=").append(typeToString(mType)); 616 switch (mType) { 617 case TYPE_BITMAP: 618 sb.append(" size=") 619 .append(getBitmap().getWidth()) 620 .append("x") 621 .append(getBitmap().getHeight()); 622 break; 623 case TYPE_RESOURCE: 624 sb.append(" pkg=") 625 .append(getResPackage()) 626 .append(" id=") 627 .append(String.format("0x%08x", getResId())); 628 break; 629 case TYPE_DATA: 630 sb.append(" len=").append(getDataLength()); 631 if (getDataOffset() != 0) { 632 sb.append(" off=").append(getDataOffset()); 633 } 634 break; 635 case TYPE_URI: 636 sb.append(" uri=").append(getUriString()); 637 break; 638 } 639 if (mTintList != null) { 640 sb.append(" tint="); 641 String sep = ""; 642 for (int c : mTintList.getColors()) { 643 sb.append(String.format("%s0x%08x", sep, c)); 644 sep = "|"; 645 } 646 } 647 if (mTintMode != DEFAULT_TINT_MODE) sb.append(" mode=").append(mTintMode); 648 sb.append(")"); 649 return sb.toString(); 650 } 651 652 /** 653 * Parcelable interface 654 */ 655 public int describeContents() { 656 return (mType == TYPE_BITMAP || mType == TYPE_DATA) 657 ? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0; 658 } 659 660 // ===== Parcelable interface ====== 661 662 private Icon(Parcel in) { 663 this(in.readInt()); 664 switch (mType) { 665 case TYPE_BITMAP: 666 final Bitmap bits = Bitmap.CREATOR.createFromParcel(in); 667 mObj1 = bits; 668 break; 669 case TYPE_RESOURCE: 670 final String pkg = in.readString(); 671 final int resId = in.readInt(); 672 mString1 = pkg; 673 mInt1 = resId; 674 break; 675 case TYPE_DATA: 676 final int len = in.readInt(); 677 final byte[] a = in.readBlob(); 678 if (len != a.length) { 679 throw new RuntimeException("internal unparceling error: blob length (" 680 + a.length + ") != expected length (" + len + ")"); 681 } 682 mInt1 = len; 683 mObj1 = a; 684 break; 685 case TYPE_URI: 686 final String uri = in.readString(); 687 mString1 = uri; 688 break; 689 default: 690 throw new RuntimeException("invalid " 691 + this.getClass().getSimpleName() + " type in parcel: " + mType); 692 } 693 if (in.readInt() == 1) { 694 mTintList = ColorStateList.CREATOR.createFromParcel(in); 695 } 696 mTintMode = PorterDuff.intToMode(in.readInt()); 697 } 698 699 @Override 700 public void writeToParcel(Parcel dest, int flags) { 701 dest.writeInt(mType); 702 switch (mType) { 703 case TYPE_BITMAP: 704 final Bitmap bits = getBitmap(); 705 getBitmap().writeToParcel(dest, flags); 706 break; 707 case TYPE_RESOURCE: 708 dest.writeString(getResPackage()); 709 dest.writeInt(getResId()); 710 break; 711 case TYPE_DATA: 712 dest.writeInt(getDataLength()); 713 dest.writeBlob(getDataBytes(), getDataOffset(), getDataLength()); 714 break; 715 case TYPE_URI: 716 dest.writeString(getUriString()); 717 break; 718 } 719 if (mTintList == null) { 720 dest.writeInt(0); 721 } else { 722 dest.writeInt(1); 723 mTintList.writeToParcel(dest, flags); 724 } 725 dest.writeInt(PorterDuff.modeToInt(mTintMode)); 726 } 727 728 public static final Parcelable.Creator<Icon> CREATOR 729 = new Parcelable.Creator<Icon>() { 730 public Icon createFromParcel(Parcel in) { 731 return new Icon(in); 732 } 733 734 public Icon[] newArray(int size) { 735 return new Icon[size]; 736 } 737 }; 738 739 /** 740 * Implement this interface to receive a callback when 741 * {@link #loadDrawableAsync(Context, OnDrawableLoadedListener, Handler) loadDrawableAsync} 742 * is finished and your Drawable is ready. 743 */ 744 public interface OnDrawableLoadedListener { 745 void onDrawableLoaded(Drawable d); 746 } 747 748 /** 749 * Wrapper around loadDrawable that does its work on a pooled thread and then 750 * fires back the given (targeted) Message. 751 */ 752 private class LoadDrawableTask implements Runnable { 753 final Context mContext; 754 final Message mMessage; 755 756 public LoadDrawableTask(Context context, final Handler handler, 757 final OnDrawableLoadedListener listener) { 758 mContext = context; 759 mMessage = Message.obtain(handler, new Runnable() { 760 @Override 761 public void run() { 762 listener.onDrawableLoaded((Drawable) mMessage.obj); 763 } 764 }); 765 } 766 767 public LoadDrawableTask(Context context, Message message) { 768 mContext = context; 769 mMessage = message; 770 } 771 772 @Override 773 public void run() { 774 mMessage.obj = loadDrawable(mContext); 775 mMessage.sendToTarget(); 776 } 777 778 public void runAsync() { 779 AsyncTask.THREAD_POOL_EXECUTOR.execute(this); 780 } 781 } 782} 783