• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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