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