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