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 import 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 59 public 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 */ getType()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 */ getBitmap()114 public Bitmap getBitmap() { 115 if (mType != TYPE_BITMAP) { 116 throw new IllegalStateException("called getBitmap() on " + this); 117 } 118 return (Bitmap) mObj1; 119 } 120 setBitmap(Bitmap b)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 */ getDataLength()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 */ getDataOffset()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 */ getDataBytes()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 */ getResources()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 */ getResPackage()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 */ getResId()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 */ getUriString()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 */ getUri()214 public Uri getUri() { 215 return Uri.parse(getUriString()); 216 } 217 typeToString(int x)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 */ loadDrawableAsync(Context context, Message andThen)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 and notifies the <code> 247 * {@link OnDrawableLoadedListener#onDrawableLoaded listener} </code> on the {@code handler} 248 * when finished. 249 * 250 * @param context {@link Context Context} in which to load the drawable; see 251 * {@link #loadDrawable(Context)} 252 * @param listener to be {@link OnDrawableLoadedListener#onDrawableLoaded notified} when 253 * {@link #loadDrawable(Context)} finished 254 * @param handler {@link Handler} on which to notify the {@code listener} 255 */ loadDrawableAsync(Context context, final OnDrawableLoadedListener listener, Handler handler)256 public void loadDrawableAsync(Context context, final OnDrawableLoadedListener listener, 257 Handler handler) { 258 new LoadDrawableTask(context, handler, listener).runAsync(); 259 } 260 261 /** 262 * Returns a Drawable that can be used to draw the image inside this Icon, constructing it 263 * if necessary. Depending on the type of image, this may not be something you want to do on 264 * the UI thread, so consider using 265 * {@link #loadDrawableAsync(Context, Message) loadDrawableAsync} instead. 266 * 267 * @param context {@link android.content.Context Context} in which to load the drawable; used 268 * to access {@link android.content.res.Resources Resources}, for example. 269 * @return A fresh instance of a drawable for this image, yours to keep. 270 */ loadDrawable(Context context)271 public Drawable loadDrawable(Context context) { 272 final Drawable result = loadDrawableInner(context); 273 if (result != null && (mTintList != null || mTintMode != DEFAULT_TINT_MODE)) { 274 result.mutate(); 275 result.setTintList(mTintList); 276 result.setTintMode(mTintMode); 277 } 278 return result; 279 } 280 281 /** 282 * Do the heavy lifting of loading the drawable, but stop short of applying any tint. 283 */ loadDrawableInner(Context context)284 private Drawable loadDrawableInner(Context context) { 285 switch (mType) { 286 case TYPE_BITMAP: 287 return new BitmapDrawable(context.getResources(), getBitmap()); 288 case TYPE_RESOURCE: 289 if (getResources() == null) { 290 // figure out where to load resources from 291 String resPackage = getResPackage(); 292 if (TextUtils.isEmpty(resPackage)) { 293 // if none is specified, try the given context 294 resPackage = context.getPackageName(); 295 } 296 if ("android".equals(resPackage)) { 297 mObj1 = Resources.getSystem(); 298 } else { 299 final PackageManager pm = context.getPackageManager(); 300 try { 301 ApplicationInfo ai = pm.getApplicationInfo( 302 resPackage, PackageManager.GET_UNINSTALLED_PACKAGES); 303 if (ai != null) { 304 mObj1 = pm.getResourcesForApplication(ai); 305 } else { 306 break; 307 } 308 } catch (PackageManager.NameNotFoundException e) { 309 Log.e(TAG, String.format("Unable to find pkg=%s for icon %s", 310 resPackage, this), e); 311 break; 312 } 313 } 314 } 315 try { 316 return getResources().getDrawable(getResId(), context.getTheme()); 317 } catch (RuntimeException e) { 318 Log.e(TAG, String.format("Unable to load resource 0x%08x from pkg=%s", 319 getResId(), 320 getResPackage()), 321 e); 322 } 323 break; 324 case TYPE_DATA: 325 return new BitmapDrawable(context.getResources(), 326 BitmapFactory.decodeByteArray(getDataBytes(), getDataOffset(), getDataLength()) 327 ); 328 case TYPE_URI: 329 final Uri uri = getUri(); 330 final String scheme = uri.getScheme(); 331 InputStream is = null; 332 if (ContentResolver.SCHEME_CONTENT.equals(scheme) 333 || ContentResolver.SCHEME_FILE.equals(scheme)) { 334 try { 335 is = context.getContentResolver().openInputStream(uri); 336 } catch (Exception e) { 337 Log.w(TAG, "Unable to load image from URI: " + uri, e); 338 } 339 } else { 340 try { 341 is = new FileInputStream(new File(mString1)); 342 } catch (FileNotFoundException e) { 343 Log.w(TAG, "Unable to load image from path: " + uri, e); 344 } 345 } 346 if (is != null) { 347 return new BitmapDrawable(context.getResources(), 348 BitmapFactory.decodeStream(is)); 349 } 350 break; 351 } 352 return null; 353 } 354 355 /** 356 * Load the requested resources under the given userId, if the system allows it, 357 * before actually loading the drawable. 358 * 359 * @hide 360 */ loadDrawableAsUser(Context context, int userId)361 public Drawable loadDrawableAsUser(Context context, int userId) { 362 if (mType == TYPE_RESOURCE) { 363 String resPackage = getResPackage(); 364 if (TextUtils.isEmpty(resPackage)) { 365 resPackage = context.getPackageName(); 366 } 367 if (getResources() == null && !(getResPackage().equals("android"))) { 368 final PackageManager pm = context.getPackageManager(); 369 try { 370 // assign getResources() as the correct user 371 mObj1 = pm.getResourcesForApplicationAsUser(resPackage, userId); 372 } catch (PackageManager.NameNotFoundException e) { 373 Log.e(TAG, String.format("Unable to find pkg=%s user=%d", 374 getResPackage(), 375 userId), 376 e); 377 } 378 } 379 } 380 return loadDrawable(context); 381 } 382 383 /** @hide */ 384 public static final int MIN_ASHMEM_ICON_SIZE = 128 * (1 << 10); 385 386 /** 387 * Puts the memory used by this instance into Ashmem memory, if possible. 388 * @hide 389 */ convertToAshmem()390 public void convertToAshmem() { 391 if (mType == TYPE_BITMAP && 392 getBitmap().isMutable() && 393 getBitmap().getAllocationByteCount() >= MIN_ASHMEM_ICON_SIZE) { 394 setBitmap(getBitmap().createAshmemBitmap()); 395 } 396 } 397 398 /** 399 * Writes a serialized version of an Icon to the specified stream. 400 * 401 * @param stream The stream on which to serialize the Icon. 402 * @hide 403 */ writeToStream(OutputStream stream)404 public void writeToStream(OutputStream stream) throws IOException { 405 DataOutputStream dataStream = new DataOutputStream(stream); 406 407 dataStream.writeInt(VERSION_STREAM_SERIALIZER); 408 dataStream.writeByte(mType); 409 410 switch (mType) { 411 case TYPE_BITMAP: 412 getBitmap().compress(Bitmap.CompressFormat.PNG, 100, dataStream); 413 break; 414 case TYPE_DATA: 415 dataStream.writeInt(getDataLength()); 416 dataStream.write(getDataBytes(), getDataOffset(), getDataLength()); 417 break; 418 case TYPE_RESOURCE: 419 dataStream.writeUTF(getResPackage()); 420 dataStream.writeInt(getResId()); 421 break; 422 case TYPE_URI: 423 dataStream.writeUTF(getUriString()); 424 break; 425 } 426 } 427 Icon(int mType)428 private Icon(int mType) { 429 this.mType = mType; 430 } 431 432 /** 433 * Create an Icon from the specified stream. 434 * 435 * @param stream The input stream from which to reconstruct the Icon. 436 * @hide 437 */ createFromStream(InputStream stream)438 public static Icon createFromStream(InputStream stream) throws IOException { 439 DataInputStream inputStream = new DataInputStream(stream); 440 441 final int version = inputStream.readInt(); 442 if (version >= VERSION_STREAM_SERIALIZER) { 443 final int type = inputStream.readByte(); 444 switch (type) { 445 case TYPE_BITMAP: 446 return createWithBitmap(BitmapFactory.decodeStream(inputStream)); 447 case TYPE_DATA: 448 final int length = inputStream.readInt(); 449 final byte[] data = new byte[length]; 450 inputStream.read(data, 0 /* offset */, length); 451 return createWithData(data, 0 /* offset */, length); 452 case TYPE_RESOURCE: 453 final String packageName = inputStream.readUTF(); 454 final int resId = inputStream.readInt(); 455 return createWithResource(packageName, resId); 456 case TYPE_URI: 457 final String uriOrPath = inputStream.readUTF(); 458 return createWithContentUri(uriOrPath); 459 } 460 } 461 return null; 462 } 463 464 /** 465 * Compares if this icon is constructed from the same resources as another icon. 466 * Note that this is an inexpensive operation and doesn't do deep Bitmap equality comparisons. 467 * 468 * @param otherIcon the other icon 469 * @return whether this icon is the same as the another one 470 * @hide 471 */ sameAs(Icon otherIcon)472 public boolean sameAs(Icon otherIcon) { 473 if (otherIcon == this) { 474 return true; 475 } 476 if (mType != otherIcon.getType()) { 477 return false; 478 } 479 switch (mType) { 480 case TYPE_BITMAP: 481 return getBitmap() == otherIcon.getBitmap(); 482 case TYPE_DATA: 483 return getDataLength() == otherIcon.getDataLength() 484 && getDataOffset() == otherIcon.getDataOffset() 485 && getDataBytes() == otherIcon.getDataBytes(); 486 case TYPE_RESOURCE: 487 return getResId() == otherIcon.getResId() 488 && Objects.equals(getResPackage(), otherIcon.getResPackage()); 489 case TYPE_URI: 490 return Objects.equals(getUriString(), otherIcon.getUriString()); 491 } 492 return false; 493 } 494 495 /** 496 * Create an Icon pointing to a drawable resource. 497 * @param context The context for the application whose resources should be used to resolve the 498 * given resource ID. 499 * @param resId ID of the drawable resource 500 */ createWithResource(Context context, @DrawableRes int resId)501 public static Icon createWithResource(Context context, @DrawableRes int resId) { 502 if (context == null) { 503 throw new IllegalArgumentException("Context must not be null."); 504 } 505 final Icon rep = new Icon(TYPE_RESOURCE); 506 rep.mInt1 = resId; 507 rep.mString1 = context.getPackageName(); 508 return rep; 509 } 510 511 /** 512 * Version of createWithResource that takes Resources. Do not use. 513 * @hide 514 */ createWithResource(Resources res, @DrawableRes int resId)515 public static Icon createWithResource(Resources res, @DrawableRes int resId) { 516 if (res == null) { 517 throw new IllegalArgumentException("Resource must not be null."); 518 } 519 final Icon rep = new Icon(TYPE_RESOURCE); 520 rep.mInt1 = resId; 521 rep.mString1 = res.getResourcePackageName(resId); 522 return rep; 523 } 524 525 /** 526 * Create an Icon pointing to a drawable resource. 527 * @param resPackage Name of the package containing the resource in question 528 * @param resId ID of the drawable resource 529 */ createWithResource(String resPackage, @DrawableRes int resId)530 public static Icon createWithResource(String resPackage, @DrawableRes int resId) { 531 if (resPackage == null) { 532 throw new IllegalArgumentException("Resource package name must not be null."); 533 } 534 final Icon rep = new Icon(TYPE_RESOURCE); 535 rep.mInt1 = resId; 536 rep.mString1 = resPackage; 537 return rep; 538 } 539 540 /** 541 * Create an Icon pointing to a bitmap in memory. 542 * @param bits A valid {@link android.graphics.Bitmap} object 543 */ createWithBitmap(Bitmap bits)544 public static Icon createWithBitmap(Bitmap bits) { 545 if (bits == null) { 546 throw new IllegalArgumentException("Bitmap must not be null."); 547 } 548 final Icon rep = new Icon(TYPE_BITMAP); 549 rep.setBitmap(bits); 550 return rep; 551 } 552 553 /** 554 * Create an Icon pointing to a compressed bitmap stored in a byte array. 555 * @param data Byte array storing compressed bitmap data of a type that 556 * {@link android.graphics.BitmapFactory} 557 * can decode (see {@link android.graphics.Bitmap.CompressFormat}). 558 * @param offset Offset into <code>data</code> at which the bitmap data starts 559 * @param length Length of the bitmap data 560 */ createWithData(byte[] data, int offset, int length)561 public static Icon createWithData(byte[] data, int offset, int length) { 562 if (data == null) { 563 throw new IllegalArgumentException("Data must not be null."); 564 } 565 final Icon rep = new Icon(TYPE_DATA); 566 rep.mObj1 = data; 567 rep.mInt1 = length; 568 rep.mInt2 = offset; 569 return rep; 570 } 571 572 /** 573 * Create an Icon pointing to an image file specified by URI. 574 * 575 * @param uri A uri referring to local content:// or file:// image data. 576 */ createWithContentUri(String uri)577 public static Icon createWithContentUri(String uri) { 578 if (uri == null) { 579 throw new IllegalArgumentException("Uri must not be null."); 580 } 581 final Icon rep = new Icon(TYPE_URI); 582 rep.mString1 = uri; 583 return rep; 584 } 585 586 /** 587 * Create an Icon pointing to an image file specified by URI. 588 * 589 * @param uri A uri referring to local content:// or file:// image data. 590 */ createWithContentUri(Uri uri)591 public static Icon createWithContentUri(Uri uri) { 592 if (uri == null) { 593 throw new IllegalArgumentException("Uri must not be null."); 594 } 595 final Icon rep = new Icon(TYPE_URI); 596 rep.mString1 = uri.toString(); 597 return rep; 598 } 599 600 /** 601 * Store a color to use whenever this Icon is drawn. 602 * 603 * @param tint a color, as in {@link Drawable#setTint(int)} 604 * @return this same object, for use in chained construction 605 */ setTint(@olorInt int tint)606 public Icon setTint(@ColorInt int tint) { 607 return setTintList(ColorStateList.valueOf(tint)); 608 } 609 610 /** 611 * Store a color to use whenever this Icon is drawn. 612 * 613 * @param tintList as in {@link Drawable#setTintList(ColorStateList)}, null to remove tint 614 * @return this same object, for use in chained construction 615 */ setTintList(ColorStateList tintList)616 public Icon setTintList(ColorStateList tintList) { 617 mTintList = tintList; 618 return this; 619 } 620 621 /** 622 * Store a blending mode to use whenever this Icon is drawn. 623 * 624 * @param mode a blending mode, as in {@link Drawable#setTintMode(PorterDuff.Mode)}, may be null 625 * @return this same object, for use in chained construction 626 */ setTintMode(PorterDuff.Mode mode)627 public Icon setTintMode(PorterDuff.Mode mode) { 628 mTintMode = mode; 629 return this; 630 } 631 632 /** @hide */ hasTint()633 public boolean hasTint() { 634 return (mTintList != null) || (mTintMode != DEFAULT_TINT_MODE); 635 } 636 637 /** 638 * Create an Icon pointing to an image file specified by path. 639 * 640 * @param path A path to a file that contains compressed bitmap data of 641 * a type that {@link android.graphics.BitmapFactory} can decode. 642 */ createWithFilePath(String path)643 public static Icon createWithFilePath(String path) { 644 if (path == null) { 645 throw new IllegalArgumentException("Path must not be null."); 646 } 647 final Icon rep = new Icon(TYPE_URI); 648 rep.mString1 = path; 649 return rep; 650 } 651 652 @Override toString()653 public String toString() { 654 final StringBuilder sb = new StringBuilder("Icon(typ=").append(typeToString(mType)); 655 switch (mType) { 656 case TYPE_BITMAP: 657 sb.append(" size=") 658 .append(getBitmap().getWidth()) 659 .append("x") 660 .append(getBitmap().getHeight()); 661 break; 662 case TYPE_RESOURCE: 663 sb.append(" pkg=") 664 .append(getResPackage()) 665 .append(" id=") 666 .append(String.format("0x%08x", getResId())); 667 break; 668 case TYPE_DATA: 669 sb.append(" len=").append(getDataLength()); 670 if (getDataOffset() != 0) { 671 sb.append(" off=").append(getDataOffset()); 672 } 673 break; 674 case TYPE_URI: 675 sb.append(" uri=").append(getUriString()); 676 break; 677 } 678 if (mTintList != null) { 679 sb.append(" tint="); 680 String sep = ""; 681 for (int c : mTintList.getColors()) { 682 sb.append(String.format("%s0x%08x", sep, c)); 683 sep = "|"; 684 } 685 } 686 if (mTintMode != DEFAULT_TINT_MODE) sb.append(" mode=").append(mTintMode); 687 sb.append(")"); 688 return sb.toString(); 689 } 690 691 /** 692 * Parcelable interface 693 */ describeContents()694 public int describeContents() { 695 return (mType == TYPE_BITMAP || mType == TYPE_DATA) 696 ? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0; 697 } 698 699 // ===== Parcelable interface ====== 700 Icon(Parcel in)701 private Icon(Parcel in) { 702 this(in.readInt()); 703 switch (mType) { 704 case TYPE_BITMAP: 705 final Bitmap bits = Bitmap.CREATOR.createFromParcel(in); 706 mObj1 = bits; 707 break; 708 case TYPE_RESOURCE: 709 final String pkg = in.readString(); 710 final int resId = in.readInt(); 711 mString1 = pkg; 712 mInt1 = resId; 713 break; 714 case TYPE_DATA: 715 final int len = in.readInt(); 716 final byte[] a = in.readBlob(); 717 if (len != a.length) { 718 throw new RuntimeException("internal unparceling error: blob length (" 719 + a.length + ") != expected length (" + len + ")"); 720 } 721 mInt1 = len; 722 mObj1 = a; 723 break; 724 case TYPE_URI: 725 final String uri = in.readString(); 726 mString1 = uri; 727 break; 728 default: 729 throw new RuntimeException("invalid " 730 + this.getClass().getSimpleName() + " type in parcel: " + mType); 731 } 732 if (in.readInt() == 1) { 733 mTintList = ColorStateList.CREATOR.createFromParcel(in); 734 } 735 mTintMode = PorterDuff.intToMode(in.readInt()); 736 } 737 738 @Override writeToParcel(Parcel dest, int flags)739 public void writeToParcel(Parcel dest, int flags) { 740 dest.writeInt(mType); 741 switch (mType) { 742 case TYPE_BITMAP: 743 final Bitmap bits = getBitmap(); 744 getBitmap().writeToParcel(dest, flags); 745 break; 746 case TYPE_RESOURCE: 747 dest.writeString(getResPackage()); 748 dest.writeInt(getResId()); 749 break; 750 case TYPE_DATA: 751 dest.writeInt(getDataLength()); 752 dest.writeBlob(getDataBytes(), getDataOffset(), getDataLength()); 753 break; 754 case TYPE_URI: 755 dest.writeString(getUriString()); 756 break; 757 } 758 if (mTintList == null) { 759 dest.writeInt(0); 760 } else { 761 dest.writeInt(1); 762 mTintList.writeToParcel(dest, flags); 763 } 764 dest.writeInt(PorterDuff.modeToInt(mTintMode)); 765 } 766 767 public static final Parcelable.Creator<Icon> CREATOR 768 = new Parcelable.Creator<Icon>() { 769 public Icon createFromParcel(Parcel in) { 770 return new Icon(in); 771 } 772 773 public Icon[] newArray(int size) { 774 return new Icon[size]; 775 } 776 }; 777 778 /** 779 * Implement this interface to receive a callback when 780 * {@link #loadDrawableAsync(Context, OnDrawableLoadedListener, Handler) loadDrawableAsync} 781 * is finished and your Drawable is ready. 782 */ 783 public interface OnDrawableLoadedListener { onDrawableLoaded(Drawable d)784 void onDrawableLoaded(Drawable d); 785 } 786 787 /** 788 * Wrapper around loadDrawable that does its work on a pooled thread and then 789 * fires back the given (targeted) Message. 790 */ 791 private class LoadDrawableTask implements Runnable { 792 final Context mContext; 793 final Message mMessage; 794 LoadDrawableTask(Context context, final Handler handler, final OnDrawableLoadedListener listener)795 public LoadDrawableTask(Context context, final Handler handler, 796 final OnDrawableLoadedListener listener) { 797 mContext = context; 798 mMessage = Message.obtain(handler, new Runnable() { 799 @Override 800 public void run() { 801 listener.onDrawableLoaded((Drawable) mMessage.obj); 802 } 803 }); 804 } 805 LoadDrawableTask(Context context, Message message)806 public LoadDrawableTask(Context context, Message message) { 807 mContext = context; 808 mMessage = message; 809 } 810 811 @Override run()812 public void run() { 813 mMessage.obj = loadDrawable(mContext); 814 mMessage.sendToTarget(); 815 } 816 runAsync()817 public void runAsync() { 818 AsyncTask.THREAD_POOL_EXECUTOR.execute(this); 819 } 820 } 821 } 822