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