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