Icon.java revision ea4bef7386ca6c6260f292bf006d16a99b93f698
146e146e0cef555379699f06edb3b2d9673978703David Gross/* 246e146e0cef555379699f06edb3b2d9673978703David Gross * Copyright (C) 2015 The Android Open Source Project 346e146e0cef555379699f06edb3b2d9673978703David Gross * 446e146e0cef555379699f06edb3b2d9673978703David Gross * Licensed under the Apache License, Version 2.0 (the "License"); 546e146e0cef555379699f06edb3b2d9673978703David Gross * you may not use this file except in compliance with the License. 646e146e0cef555379699f06edb3b2d9673978703David Gross * You may obtain a copy of the License at 746e146e0cef555379699f06edb3b2d9673978703David Gross * 846e146e0cef555379699f06edb3b2d9673978703David Gross * http://www.apache.org/licenses/LICENSE-2.0 946e146e0cef555379699f06edb3b2d9673978703David Gross * 1046e146e0cef555379699f06edb3b2d9673978703David Gross * Unless required by applicable law or agreed to in writing, software 1146e146e0cef555379699f06edb3b2d9673978703David Gross * distributed under the License is distributed on an "AS IS" BASIS, 1246e146e0cef555379699f06edb3b2d9673978703David Gross * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1346e146e0cef555379699f06edb3b2d9673978703David Gross * See the License for the specific language governing permissions and 1446e146e0cef555379699f06edb3b2d9673978703David Gross * limitations under the License. 1546e146e0cef555379699f06edb3b2d9673978703David Gross */ 1646e146e0cef555379699f06edb3b2d9673978703David Gross 1746e146e0cef555379699f06edb3b2d9673978703David Grosspackage android.graphics.drawable; 1846e146e0cef555379699f06edb3b2d9673978703David Gross 1946e146e0cef555379699f06edb3b2d9673978703David Grossimport android.annotation.ColorInt; 2046e146e0cef555379699f06edb3b2d9673978703David Grossimport android.annotation.DrawableRes; 2146e146e0cef555379699f06edb3b2d9673978703David Grossimport android.content.res.ColorStateList; 2246e146e0cef555379699f06edb3b2d9673978703David Grossimport android.content.ContentResolver; 2346e146e0cef555379699f06edb3b2d9673978703David Grossimport android.content.Context; 2446e146e0cef555379699f06edb3b2d9673978703David Grossimport android.content.pm.ApplicationInfo; 2546e146e0cef555379699f06edb3b2d9673978703David Grossimport android.content.pm.PackageManager; 2646e146e0cef555379699f06edb3b2d9673978703David Grossimport android.content.res.Resources; 2746e146e0cef555379699f06edb3b2d9673978703David Grossimport android.graphics.Bitmap; 2846e146e0cef555379699f06edb3b2d9673978703David Grossimport android.graphics.BitmapFactory; 2946e146e0cef555379699f06edb3b2d9673978703David Grossimport android.graphics.PorterDuff; 3046e146e0cef555379699f06edb3b2d9673978703David Grossimport android.net.Uri; 3146e146e0cef555379699f06edb3b2d9673978703David Grossimport android.os.AsyncTask; 3246e146e0cef555379699f06edb3b2d9673978703David Grossimport android.os.Handler; 3346e146e0cef555379699f06edb3b2d9673978703David Grossimport android.os.Message; 3446e146e0cef555379699f06edb3b2d9673978703David Grossimport android.os.Parcel; 3546e146e0cef555379699f06edb3b2d9673978703David Grossimport android.os.Parcelable; 3646e146e0cef555379699f06edb3b2d9673978703David Grossimport android.text.TextUtils; 3746e146e0cef555379699f06edb3b2d9673978703David Grossimport android.util.Log; 3846e146e0cef555379699f06edb3b2d9673978703David Gross 3946e146e0cef555379699f06edb3b2d9673978703David Grossimport java.io.DataInputStream; 4046e146e0cef555379699f06edb3b2d9673978703David Grossimport java.io.DataOutputStream; 4146e146e0cef555379699f06edb3b2d9673978703David Grossimport java.io.File; 4246e146e0cef555379699f06edb3b2d9673978703David Grossimport java.io.FileInputStream; 4346e146e0cef555379699f06edb3b2d9673978703David Grossimport java.io.FileNotFoundException; 4446e146e0cef555379699f06edb3b2d9673978703David Grossimport java.io.IOException; 4546e146e0cef555379699f06edb3b2d9673978703David Grossimport java.io.InputStream; 4646e146e0cef555379699f06edb3b2d9673978703David Grossimport java.io.OutputStream; 4746e146e0cef555379699f06edb3b2d9673978703David Grossimport java.util.Objects; 4846e146e0cef555379699f06edb3b2d9673978703David Gross 4946e146e0cef555379699f06edb3b2d9673978703David Gross/** 5046e146e0cef555379699f06edb3b2d9673978703David Gross * An umbrella container for several serializable graphics representations, including Bitmaps, 5146e146e0cef555379699f06edb3b2d9673978703David Gross * compressed bitmap images (e.g. JPG or PNG), and drawable resources (including vectors). 5246e146e0cef555379699f06edb3b2d9673978703David Gross * 5346e146e0cef555379699f06edb3b2d9673978703David Gross * <a href="https://developer.android.com/training/displaying-bitmaps/index.html">Much ink</a> 5446e146e0cef555379699f06edb3b2d9673978703David Gross * has been spilled on the best way to load images, and many clients may have different needs when 5546e146e0cef555379699f06edb3b2d9673978703David Gross * it comes to threading and fetching. This class is therefore focused on encapsulation rather than 5646e146e0cef555379699f06edb3b2d9673978703David Gross * behavior. 5746e146e0cef555379699f06edb3b2d9673978703David Gross */ 5846e146e0cef555379699f06edb3b2d9673978703David Gross 5946e146e0cef555379699f06edb3b2d9673978703David Grosspublic final class Icon implements Parcelable { 6046e146e0cef555379699f06edb3b2d9673978703David Gross private static final String TAG = "Icon"; 6146e146e0cef555379699f06edb3b2d9673978703David Gross 6246e146e0cef555379699f06edb3b2d9673978703David Gross /** @hide */ 6346e146e0cef555379699f06edb3b2d9673978703David Gross public static final int TYPE_BITMAP = 1; 6446e146e0cef555379699f06edb3b2d9673978703David Gross /** @hide */ 6546e146e0cef555379699f06edb3b2d9673978703David Gross public static final int TYPE_RESOURCE = 2; 6646e146e0cef555379699f06edb3b2d9673978703David Gross /** @hide */ 6746e146e0cef555379699f06edb3b2d9673978703David Gross public static final int TYPE_DATA = 3; 6846e146e0cef555379699f06edb3b2d9673978703David Gross /** @hide */ 6946e146e0cef555379699f06edb3b2d9673978703David Gross public static final int TYPE_URI = 4; 7046e146e0cef555379699f06edb3b2d9673978703David Gross 7146e146e0cef555379699f06edb3b2d9673978703David Gross private static final int VERSION_STREAM_SERIALIZER = 1; 7246e146e0cef555379699f06edb3b2d9673978703David Gross 7346e146e0cef555379699f06edb3b2d9673978703David Gross private final int mType; 7446e146e0cef555379699f06edb3b2d9673978703David Gross 7546e146e0cef555379699f06edb3b2d9673978703David Gross private ColorStateList mTintList; 7646e146e0cef555379699f06edb3b2d9673978703David Gross static final PorterDuff.Mode DEFAULT_TINT_MODE = Drawable.DEFAULT_TINT_MODE; // SRC_IN 7746e146e0cef555379699f06edb3b2d9673978703David Gross private PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE; 7846e146e0cef555379699f06edb3b2d9673978703David Gross 7946e146e0cef555379699f06edb3b2d9673978703David Gross // To avoid adding unnecessary overhead, we have a few basic objects that get repurposed 8046e146e0cef555379699f06edb3b2d9673978703David Gross // based on the value of mType. 8146e146e0cef555379699f06edb3b2d9673978703David Gross 8246e146e0cef555379699f06edb3b2d9673978703David Gross // TYPE_BITMAP: Bitmap 8346e146e0cef555379699f06edb3b2d9673978703David Gross // TYPE_RESOURCE: Resources 8446e146e0cef555379699f06edb3b2d9673978703David Gross // TYPE_DATA: DataBytes 8546e146e0cef555379699f06edb3b2d9673978703David Gross private Object mObj1; 8646e146e0cef555379699f06edb3b2d9673978703David Gross 8746e146e0cef555379699f06edb3b2d9673978703David Gross // TYPE_RESOURCE: package name 8846e146e0cef555379699f06edb3b2d9673978703David Gross // TYPE_URI: uri string 8946e146e0cef555379699f06edb3b2d9673978703David Gross private String mString1; 9046e146e0cef555379699f06edb3b2d9673978703David Gross 9146e146e0cef555379699f06edb3b2d9673978703David Gross // TYPE_RESOURCE: resId 9246e146e0cef555379699f06edb3b2d9673978703David Gross // TYPE_DATA: data length 9346e146e0cef555379699f06edb3b2d9673978703David Gross private int mInt1; 9446e146e0cef555379699f06edb3b2d9673978703David Gross 9546e146e0cef555379699f06edb3b2d9673978703David Gross // TYPE_DATA: data offset 9646e146e0cef555379699f06edb3b2d9673978703David Gross private int mInt2; 9746e146e0cef555379699f06edb3b2d9673978703David Gross 98e2ead846c1d78a6e7108e521ffd15850bd9eed3cChih-Hung Hsieh /** 9946e146e0cef555379699f06edb3b2d9673978703David Gross * @return The type of image data held in this Icon. One of 10046e146e0cef555379699f06edb3b2d9673978703David Gross * {@link #TYPE_BITMAP}, 10146e146e0cef555379699f06edb3b2d9673978703David Gross * {@link #TYPE_RESOURCE}, 10246e146e0cef555379699f06edb3b2d9673978703David Gross * {@link #TYPE_DATA}, or 10346e146e0cef555379699f06edb3b2d9673978703David Gross * {@link #TYPE_URI}. 10446e146e0cef555379699f06edb3b2d9673978703David Gross * @hide 10546e146e0cef555379699f06edb3b2d9673978703David Gross */ 10646e146e0cef555379699f06edb3b2d9673978703David Gross public int getType() { 10746e146e0cef555379699f06edb3b2d9673978703David Gross return mType; 10846e146e0cef555379699f06edb3b2d9673978703David Gross } 10946e146e0cef555379699f06edb3b2d9673978703David Gross 11046e146e0cef555379699f06edb3b2d9673978703David Gross /** 11146e146e0cef555379699f06edb3b2d9673978703David Gross * @return The {@link android.graphics.Bitmap} held by this {@link #TYPE_BITMAP} Icon. 11246e146e0cef555379699f06edb3b2d9673978703David Gross * @hide 11346e146e0cef555379699f06edb3b2d9673978703David Gross */ 11446e146e0cef555379699f06edb3b2d9673978703David Gross public Bitmap getBitmap() { 11546e146e0cef555379699f06edb3b2d9673978703David Gross if (mType != TYPE_BITMAP) { 11646e146e0cef555379699f06edb3b2d9673978703David Gross throw new IllegalStateException("called getBitmap() on " + this); 11746e146e0cef555379699f06edb3b2d9673978703David Gross } 11846e146e0cef555379699f06edb3b2d9673978703David Gross return (Bitmap) mObj1; 11946e146e0cef555379699f06edb3b2d9673978703David Gross } 12046e146e0cef555379699f06edb3b2d9673978703David Gross 12146e146e0cef555379699f06edb3b2d9673978703David Gross private void setBitmap(Bitmap b) { 12246e146e0cef555379699f06edb3b2d9673978703David Gross mObj1 = b; 12346e146e0cef555379699f06edb3b2d9673978703David Gross } 12446e146e0cef555379699f06edb3b2d9673978703David Gross 12546e146e0cef555379699f06edb3b2d9673978703David Gross /** 12646e146e0cef555379699f06edb3b2d9673978703David Gross * @return The length of the compressed bitmap byte array held by this {@link #TYPE_DATA} Icon. 12746e146e0cef555379699f06edb3b2d9673978703David Gross * @hide 12846e146e0cef555379699f06edb3b2d9673978703David Gross */ 12946e146e0cef555379699f06edb3b2d9673978703David Gross public int getDataLength() { 13046e146e0cef555379699f06edb3b2d9673978703David Gross if (mType != TYPE_DATA) { 13146e146e0cef555379699f06edb3b2d9673978703David Gross throw new IllegalStateException("called getDataLength() on " + this); 13246e146e0cef555379699f06edb3b2d9673978703David Gross } 13346e146e0cef555379699f06edb3b2d9673978703David Gross synchronized (this) { 13446e146e0cef555379699f06edb3b2d9673978703David Gross return mInt1; 13546e146e0cef555379699f06edb3b2d9673978703David Gross } 13646e146e0cef555379699f06edb3b2d9673978703David Gross } 13746e146e0cef555379699f06edb3b2d9673978703David Gross 13846e146e0cef555379699f06edb3b2d9673978703David Gross /** 13946e146e0cef555379699f06edb3b2d9673978703David Gross * @return The offset into the byte array held by this {@link #TYPE_DATA} Icon at which 14046e146e0cef555379699f06edb3b2d9673978703David Gross * valid compressed bitmap data is found. 14146e146e0cef555379699f06edb3b2d9673978703David Gross * @hide 14246e146e0cef555379699f06edb3b2d9673978703David Gross */ 14346e146e0cef555379699f06edb3b2d9673978703David Gross public int getDataOffset() { 14446e146e0cef555379699f06edb3b2d9673978703David Gross if (mType != TYPE_DATA) { 14546e146e0cef555379699f06edb3b2d9673978703David Gross throw new IllegalStateException("called getDataOffset() on " + this); 14646e146e0cef555379699f06edb3b2d9673978703David Gross } 14746e146e0cef555379699f06edb3b2d9673978703David Gross synchronized (this) { 14846e146e0cef555379699f06edb3b2d9673978703David Gross return mInt2; 14946e146e0cef555379699f06edb3b2d9673978703David Gross } 15046e146e0cef555379699f06edb3b2d9673978703David Gross } 15146e146e0cef555379699f06edb3b2d9673978703David Gross 15246e146e0cef555379699f06edb3b2d9673978703David Gross /** 15346e146e0cef555379699f06edb3b2d9673978703David Gross * @return The byte array held by this {@link #TYPE_DATA} Icon ctonaining compressed 15446e146e0cef555379699f06edb3b2d9673978703David Gross * bitmap data. 15546e146e0cef555379699f06edb3b2d9673978703David Gross * @hide 15646e146e0cef555379699f06edb3b2d9673978703David Gross */ 15746e146e0cef555379699f06edb3b2d9673978703David Gross public byte[] getDataBytes() { 15846e146e0cef555379699f06edb3b2d9673978703David Gross if (mType != TYPE_DATA) { 15946e146e0cef555379699f06edb3b2d9673978703David Gross throw new IllegalStateException("called getDataBytes() on " + this); 16046e146e0cef555379699f06edb3b2d9673978703David Gross } 16146e146e0cef555379699f06edb3b2d9673978703David Gross synchronized (this) { 16246e146e0cef555379699f06edb3b2d9673978703David Gross return (byte[]) mObj1; 16346e146e0cef555379699f06edb3b2d9673978703David Gross } 16446e146e0cef555379699f06edb3b2d9673978703David Gross } 16546e146e0cef555379699f06edb3b2d9673978703David Gross 16646e146e0cef555379699f06edb3b2d9673978703David Gross /** 16746e146e0cef555379699f06edb3b2d9673978703David Gross * @return The {@link android.content.res.Resources} for this {@link #TYPE_RESOURCE} Icon. 16846e146e0cef555379699f06edb3b2d9673978703David Gross * @hide 16946e146e0cef555379699f06edb3b2d9673978703David Gross */ 17046e146e0cef555379699f06edb3b2d9673978703David Gross public Resources getResources() { 17146e146e0cef555379699f06edb3b2d9673978703David Gross if (mType != TYPE_RESOURCE) { 17246e146e0cef555379699f06edb3b2d9673978703David Gross throw new IllegalStateException("called getResources() on " + this); 17346e146e0cef555379699f06edb3b2d9673978703David Gross } 17446e146e0cef555379699f06edb3b2d9673978703David Gross return (Resources) mObj1; 17546e146e0cef555379699f06edb3b2d9673978703David Gross } 17646e146e0cef555379699f06edb3b2d9673978703David Gross 17746e146e0cef555379699f06edb3b2d9673978703David Gross /** 17846e146e0cef555379699f06edb3b2d9673978703David Gross * @return The package containing resources for this {@link #TYPE_RESOURCE} Icon. 17946e146e0cef555379699f06edb3b2d9673978703David Gross * @hide 18046e146e0cef555379699f06edb3b2d9673978703David Gross */ 18146e146e0cef555379699f06edb3b2d9673978703David Gross public String getResPackage() { 18246e146e0cef555379699f06edb3b2d9673978703David Gross if (mType != TYPE_RESOURCE) { 18346e146e0cef555379699f06edb3b2d9673978703David Gross throw new IllegalStateException("called getResPackage() on " + this); 18446e146e0cef555379699f06edb3b2d9673978703David Gross } 18546e146e0cef555379699f06edb3b2d9673978703David Gross return mString1; 18646e146e0cef555379699f06edb3b2d9673978703David Gross } 18746e146e0cef555379699f06edb3b2d9673978703David Gross 18846e146e0cef555379699f06edb3b2d9673978703David Gross /** 18946e146e0cef555379699f06edb3b2d9673978703David Gross * @return The resource ID for this {@link #TYPE_RESOURCE} Icon. 19046e146e0cef555379699f06edb3b2d9673978703David Gross * @hide 19146e146e0cef555379699f06edb3b2d9673978703David Gross */ 19246e146e0cef555379699f06edb3b2d9673978703David Gross public int getResId() { 19346e146e0cef555379699f06edb3b2d9673978703David Gross if (mType != TYPE_RESOURCE) { 19446e146e0cef555379699f06edb3b2d9673978703David Gross throw new IllegalStateException("called getResId() on " + this); 19546e146e0cef555379699f06edb3b2d9673978703David Gross } 19646e146e0cef555379699f06edb3b2d9673978703David Gross return mInt1; 19746e146e0cef555379699f06edb3b2d9673978703David Gross } 19846e146e0cef555379699f06edb3b2d9673978703David Gross 19946e146e0cef555379699f06edb3b2d9673978703David Gross /** 20046e146e0cef555379699f06edb3b2d9673978703David Gross * @return The URI (as a String) for this {@link #TYPE_URI} Icon. 20146e146e0cef555379699f06edb3b2d9673978703David Gross * @hide 20246e146e0cef555379699f06edb3b2d9673978703David Gross */ 20346e146e0cef555379699f06edb3b2d9673978703David Gross public String getUriString() { 20446e146e0cef555379699f06edb3b2d9673978703David Gross if (mType != TYPE_URI) { 20546e146e0cef555379699f06edb3b2d9673978703David Gross throw new IllegalStateException("called getUriString() on " + this); 20646e146e0cef555379699f06edb3b2d9673978703David Gross } 20746e146e0cef555379699f06edb3b2d9673978703David Gross return mString1; 20846e146e0cef555379699f06edb3b2d9673978703David Gross } 20946e146e0cef555379699f06edb3b2d9673978703David Gross 21046e146e0cef555379699f06edb3b2d9673978703David Gross /** 21146e146e0cef555379699f06edb3b2d9673978703David Gross * @return The {@link android.net.Uri} for this {@link #TYPE_URI} Icon. 21246e146e0cef555379699f06edb3b2d9673978703David Gross * @hide 21346e146e0cef555379699f06edb3b2d9673978703David Gross */ 21446e146e0cef555379699f06edb3b2d9673978703David Gross public Uri getUri() { 21546e146e0cef555379699f06edb3b2d9673978703David Gross return Uri.parse(getUriString()); 21646e146e0cef555379699f06edb3b2d9673978703David Gross } 21746e146e0cef555379699f06edb3b2d9673978703David Gross 21846e146e0cef555379699f06edb3b2d9673978703David Gross private static final String typeToString(int x) { 21946e146e0cef555379699f06edb3b2d9673978703David Gross switch (x) { 22046e146e0cef555379699f06edb3b2d9673978703David Gross case TYPE_BITMAP: return "BITMAP"; 22146e146e0cef555379699f06edb3b2d9673978703David Gross case TYPE_DATA: return "DATA"; 22246e146e0cef555379699f06edb3b2d9673978703David Gross case TYPE_RESOURCE: return "RESOURCE"; 22346e146e0cef555379699f06edb3b2d9673978703David Gross case TYPE_URI: return "URI"; 22446e146e0cef555379699f06edb3b2d9673978703David Gross default: return "UNKNOWN"; 22546e146e0cef555379699f06edb3b2d9673978703David Gross } 22646e146e0cef555379699f06edb3b2d9673978703David Gross } 22746e146e0cef555379699f06edb3b2d9673978703David Gross 22846e146e0cef555379699f06edb3b2d9673978703David Gross /** 22946e146e0cef555379699f06edb3b2d9673978703David Gross * Invokes {@link #loadDrawable(Context)} on the given {@link android.os.Handler Handler} 23046e146e0cef555379699f06edb3b2d9673978703David Gross * and then sends <code>andThen</code> to the same Handler when finished. 23146e146e0cef555379699f06edb3b2d9673978703David Gross * 23246e146e0cef555379699f06edb3b2d9673978703David Gross * @param context {@link android.content.Context Context} in which to load the drawable; see 23346e146e0cef555379699f06edb3b2d9673978703David Gross * {@link #loadDrawable(Context)} 23446e146e0cef555379699f06edb3b2d9673978703David Gross * @param andThen {@link android.os.Message} to send to its target once the drawable 235 * is available. The {@link android.os.Message#obj obj} 236 * property is populated with the Drawable. 237 */ 238 public void loadDrawableAsync(Context context, Message andThen) { 239 if (andThen.getTarget() == null) { 240 throw new IllegalArgumentException("callback message must have a target handler"); 241 } 242 new LoadDrawableTask(context, andThen).runAsync(); 243 } 244 245 /** 246 * Invokes {@link #loadDrawable(Context)} on a background thread 247 * and then runs <code>andThen</code> on the UI thread when finished. 248 * 249 * @param context {@link Context Context} in which to load the drawable; see 250 * {@link #loadDrawable(Context)} 251 * @param listener a callback to run on the provided 252 * @param handler {@link Handler} on which to run <code>andThen</code>. 253 */ 254 public void loadDrawableAsync(Context context, final OnDrawableLoadedListener listener, 255 Handler handler) { 256 new LoadDrawableTask(context, handler, listener).runAsync(); 257 } 258 259 /** 260 * Returns a Drawable that can be used to draw the image inside this Icon, constructing it 261 * if necessary. Depending on the type of image, this may not be something you want to do on 262 * the UI thread, so consider using 263 * {@link #loadDrawableAsync(Context, Message) loadDrawableAsync} instead. 264 * 265 * @param context {@link android.content.Context Context} in which to load the drawable; used 266 * to access {@link android.content.res.Resources Resources}, for example. 267 * @return A fresh instance of a drawable for this image, yours to keep. 268 */ 269 public Drawable loadDrawable(Context context) { 270 final Drawable result = loadDrawableInner(context); 271 if (result != null && (mTintList != null || mTintMode != DEFAULT_TINT_MODE)) { 272 result.mutate(); 273 result.setTintList(mTintList); 274 result.setTintMode(mTintMode); 275 } 276 return result; 277 } 278 279 /** 280 * Do the heavy lifting of loading the drawable, but stop short of applying any tint. 281 */ 282 private Drawable loadDrawableInner(Context context) { 283 switch (mType) { 284 case TYPE_BITMAP: 285 return new BitmapDrawable(context.getResources(), getBitmap()); 286 case TYPE_RESOURCE: 287 if (getResources() == null) { 288 // figure out where to load resources from 289 String resPackage = getResPackage(); 290 if (TextUtils.isEmpty(resPackage)) { 291 // if none is specified, try the given context 292 resPackage = context.getPackageName(); 293 } 294 if ("android".equals(resPackage)) { 295 mObj1 = Resources.getSystem(); 296 } else { 297 final PackageManager pm = context.getPackageManager(); 298 try { 299 ApplicationInfo ai = pm.getApplicationInfo( 300 resPackage, PackageManager.GET_UNINSTALLED_PACKAGES); 301 if (ai != null) { 302 mObj1 = pm.getResourcesForApplication(ai); 303 } else { 304 break; 305 } 306 } catch (PackageManager.NameNotFoundException e) { 307 Log.e(TAG, String.format("Unable to find pkg=%s for icon %s", 308 resPackage, this), e); 309 break; 310 } 311 } 312 } 313 try { 314 return getResources().getDrawable(getResId(), context.getTheme()); 315 } catch (RuntimeException e) { 316 Log.e(TAG, String.format("Unable to load resource 0x%08x from pkg=%s", 317 getResId(), 318 getResPackage()), 319 e); 320 } 321 break; 322 case TYPE_DATA: 323 return new BitmapDrawable(context.getResources(), 324 BitmapFactory.decodeByteArray(getDataBytes(), getDataOffset(), getDataLength()) 325 ); 326 case TYPE_URI: 327 final Uri uri = getUri(); 328 final String scheme = uri.getScheme(); 329 InputStream is = null; 330 if (ContentResolver.SCHEME_CONTENT.equals(scheme) 331 || ContentResolver.SCHEME_FILE.equals(scheme)) { 332 try { 333 is = context.getContentResolver().openInputStream(uri); 334 } catch (Exception e) { 335 Log.w(TAG, "Unable to load image from URI: " + uri, e); 336 } 337 } else { 338 try { 339 is = new FileInputStream(new File(mString1)); 340 } catch (FileNotFoundException e) { 341 Log.w(TAG, "Unable to load image from path: " + uri, e); 342 } 343 } 344 if (is != null) { 345 return new BitmapDrawable(context.getResources(), 346 BitmapFactory.decodeStream(is)); 347 } 348 break; 349 } 350 return null; 351 } 352 353 /** 354 * Load the requested resources under the given userId, if the system allows it, 355 * before actually loading the drawable. 356 * 357 * @hide 358 */ 359 public Drawable loadDrawableAsUser(Context context, int userId) { 360 if (mType == TYPE_RESOURCE) { 361 String resPackage = getResPackage(); 362 if (TextUtils.isEmpty(resPackage)) { 363 resPackage = context.getPackageName(); 364 } 365 if (getResources() == null && !(getResPackage().equals("android"))) { 366 final PackageManager pm = context.getPackageManager(); 367 try { 368 // assign getResources() as the correct user 369 mObj1 = pm.getResourcesForApplicationAsUser(resPackage, userId); 370 } catch (PackageManager.NameNotFoundException e) { 371 Log.e(TAG, String.format("Unable to find pkg=%s user=%d", 372 getResPackage(), 373 userId), 374 e); 375 } 376 } 377 } 378 return loadDrawable(context); 379 } 380 381 /** @hide */ 382 public static final int MIN_ASHMEM_ICON_SIZE = 128 * (1 << 10); 383 384 /** 385 * Puts the memory used by this instance into Ashmem memory, if possible. 386 * @hide 387 */ 388 public void convertToAshmem() { 389 if (mType == TYPE_BITMAP && 390 getBitmap().isMutable() && 391 getBitmap().getAllocationByteCount() >= MIN_ASHMEM_ICON_SIZE) { 392 setBitmap(getBitmap().createAshmemBitmap()); 393 } 394 } 395 396 /** 397 * Writes a serialized version of an Icon to the specified stream. 398 * 399 * @param stream The stream on which to serialize the Icon. 400 * @hide 401 */ 402 public void writeToStream(OutputStream stream) throws IOException { 403 DataOutputStream dataStream = new DataOutputStream(stream); 404 405 dataStream.writeInt(VERSION_STREAM_SERIALIZER); 406 dataStream.writeByte(mType); 407 408 switch (mType) { 409 case TYPE_BITMAP: 410 getBitmap().compress(Bitmap.CompressFormat.PNG, 100, dataStream); 411 break; 412 case TYPE_DATA: 413 dataStream.writeInt(getDataLength()); 414 dataStream.write(getDataBytes(), getDataOffset(), getDataLength()); 415 break; 416 case TYPE_RESOURCE: 417 dataStream.writeUTF(getResPackage()); 418 dataStream.writeInt(getResId()); 419 break; 420 case TYPE_URI: 421 dataStream.writeUTF(getUriString()); 422 break; 423 } 424 } 425 426 private Icon(int mType) { 427 this.mType = mType; 428 } 429 430 /** 431 * Create an Icon from the specified stream. 432 * 433 * @param stream The input stream from which to reconstruct the Icon. 434 * @hide 435 */ 436 public static Icon createFromStream(InputStream stream) throws IOException { 437 DataInputStream inputStream = new DataInputStream(stream); 438 439 final int version = inputStream.readInt(); 440 if (version >= VERSION_STREAM_SERIALIZER) { 441 final int type = inputStream.readByte(); 442 switch (type) { 443 case TYPE_BITMAP: 444 return createWithBitmap(BitmapFactory.decodeStream(inputStream)); 445 case TYPE_DATA: 446 final int length = inputStream.readInt(); 447 final byte[] data = new byte[length]; 448 inputStream.read(data, 0 /* offset */, length); 449 return createWithData(data, 0 /* offset */, length); 450 case TYPE_RESOURCE: 451 final String packageName = inputStream.readUTF(); 452 final int resId = inputStream.readInt(); 453 return createWithResource(packageName, resId); 454 case TYPE_URI: 455 final String uriOrPath = inputStream.readUTF(); 456 return createWithContentUri(uriOrPath); 457 } 458 } 459 return null; 460 } 461 462 /** 463 * Compares if this icon is constructed from the same resources as another icon. 464 * Note that this is an inexpensive operation and doesn't do deep Bitmap equality comparisons. 465 * 466 * @param otherIcon the other icon 467 * @return whether this icon is the same as the another one 468 * @hide 469 */ 470 public boolean sameAs(Icon otherIcon) { 471 if (otherIcon == this) { 472 return true; 473 } 474 if (mType != otherIcon.getType()) { 475 return false; 476 } 477 switch (mType) { 478 case TYPE_BITMAP: 479 return getBitmap() == otherIcon.getBitmap(); 480 case TYPE_DATA: 481 return getDataLength() == otherIcon.getDataLength() 482 && getDataOffset() == otherIcon.getDataOffset() 483 && getDataBytes() == otherIcon.getDataBytes(); 484 case TYPE_RESOURCE: 485 return getResId() == otherIcon.getResId() 486 && Objects.equals(getResPackage(), otherIcon.getResPackage()); 487 case TYPE_URI: 488 return Objects.equals(getUriString(), otherIcon.getUriString()); 489 } 490 return false; 491 } 492 493 /** 494 * Create an Icon pointing to a drawable resource. 495 * @param context The context for the application whose resources should be used to resolve the 496 * given resource ID. 497 * @param resId ID of the drawable resource 498 */ 499 public static Icon createWithResource(Context context, @DrawableRes int resId) { 500 if (context == null) { 501 throw new IllegalArgumentException("Context must not be null."); 502 } 503 final Icon rep = new Icon(TYPE_RESOURCE); 504 rep.mInt1 = resId; 505 rep.mString1 = context.getPackageName(); 506 return rep; 507 } 508 509 /** 510 * Version of createWithResource that takes Resources. Do not use. 511 * @hide 512 */ 513 public static Icon createWithResource(Resources res, @DrawableRes int resId) { 514 if (res == null) { 515 throw new IllegalArgumentException("Resource must not be null."); 516 } 517 final Icon rep = new Icon(TYPE_RESOURCE); 518 rep.mInt1 = resId; 519 rep.mString1 = res.getResourcePackageName(resId); 520 return rep; 521 } 522 523 /** 524 * Create an Icon pointing to a drawable resource. 525 * @param resPackage Name of the package containing the resource in question 526 * @param resId ID of the drawable resource 527 */ 528 public static Icon createWithResource(String resPackage, @DrawableRes int resId) { 529 if (resPackage == null) { 530 throw new IllegalArgumentException("Resource package name must not be null."); 531 } 532 final Icon rep = new Icon(TYPE_RESOURCE); 533 rep.mInt1 = resId; 534 rep.mString1 = resPackage; 535 return rep; 536 } 537 538 /** 539 * Create an Icon pointing to a bitmap in memory. 540 * @param bits A valid {@link android.graphics.Bitmap} object 541 */ 542 public static Icon createWithBitmap(Bitmap bits) { 543 if (bits == null) { 544 throw new IllegalArgumentException("Bitmap must not be null."); 545 } 546 final Icon rep = new Icon(TYPE_BITMAP); 547 rep.setBitmap(bits); 548 return rep; 549 } 550 551 /** 552 * Create an Icon pointing to a compressed bitmap stored in a byte array. 553 * @param data Byte array storing compressed bitmap data of a type that 554 * {@link android.graphics.BitmapFactory} 555 * can decode (see {@link android.graphics.Bitmap.CompressFormat}). 556 * @param offset Offset into <code>data</code> at which the bitmap data starts 557 * @param length Length of the bitmap data 558 */ 559 public static Icon createWithData(byte[] data, int offset, int length) { 560 if (data == null) { 561 throw new IllegalArgumentException("Data must not be null."); 562 } 563 final Icon rep = new Icon(TYPE_DATA); 564 rep.mObj1 = data; 565 rep.mInt1 = length; 566 rep.mInt2 = offset; 567 return rep; 568 } 569 570 /** 571 * Create an Icon pointing to an image file specified by URI. 572 * 573 * @param uri A uri referring to local content:// or file:// image data. 574 */ 575 public static Icon createWithContentUri(String uri) { 576 if (uri == null) { 577 throw new IllegalArgumentException("Uri must not be null."); 578 } 579 final Icon rep = new Icon(TYPE_URI); 580 rep.mString1 = uri; 581 return rep; 582 } 583 584 /** 585 * Create an Icon pointing to an image file specified by URI. 586 * 587 * @param uri A uri referring to local content:// or file:// image data. 588 */ 589 public static Icon createWithContentUri(Uri uri) { 590 if (uri == null) { 591 throw new IllegalArgumentException("Uri must not be null."); 592 } 593 final Icon rep = new Icon(TYPE_URI); 594 rep.mString1 = uri.toString(); 595 return rep; 596 } 597 598 /** 599 * Store a color to use whenever this Icon is drawn. 600 * 601 * @param tint a color, as in {@link Drawable#setTint(int)} 602 * @return this same object, for use in chained construction 603 */ 604 public Icon setTint(@ColorInt int tint) { 605 return setTintList(ColorStateList.valueOf(tint)); 606 } 607 608 /** 609 * Store a color to use whenever this Icon is drawn. 610 * 611 * @param tintList as in {@link Drawable#setTintList(ColorStateList)}, null to remove tint 612 * @return this same object, for use in chained construction 613 */ 614 public Icon setTintList(ColorStateList tintList) { 615 mTintList = tintList; 616 return this; 617 } 618 619 /** 620 * Store a blending mode to use whenever this Icon is drawn. 621 * 622 * @param mode a blending mode, as in {@link Drawable#setTintMode(PorterDuff.Mode)}, may be null 623 * @return this same object, for use in chained construction 624 */ 625 public Icon setTintMode(PorterDuff.Mode mode) { 626 mTintMode = mode; 627 return this; 628 } 629 630 /** 631 * Create an Icon pointing to an image file specified by path. 632 * 633 * @param path A path to a file that contains compressed bitmap data of 634 * a type that {@link android.graphics.BitmapFactory} can decode. 635 */ 636 public static Icon createWithFilePath(String path) { 637 if (path == null) { 638 throw new IllegalArgumentException("Path must not be null."); 639 } 640 final Icon rep = new Icon(TYPE_URI); 641 rep.mString1 = path; 642 return rep; 643 } 644 645 @Override 646 public String toString() { 647 final StringBuilder sb = new StringBuilder("Icon(typ=").append(typeToString(mType)); 648 switch (mType) { 649 case TYPE_BITMAP: 650 sb.append(" size=") 651 .append(getBitmap().getWidth()) 652 .append("x") 653 .append(getBitmap().getHeight()); 654 break; 655 case TYPE_RESOURCE: 656 sb.append(" pkg=") 657 .append(getResPackage()) 658 .append(" id=") 659 .append(String.format("0x%08x", getResId())); 660 break; 661 case TYPE_DATA: 662 sb.append(" len=").append(getDataLength()); 663 if (getDataOffset() != 0) { 664 sb.append(" off=").append(getDataOffset()); 665 } 666 break; 667 case TYPE_URI: 668 sb.append(" uri=").append(getUriString()); 669 break; 670 } 671 if (mTintList != null) { 672 sb.append(" tint="); 673 String sep = ""; 674 for (int c : mTintList.getColors()) { 675 sb.append(String.format("%s0x%08x", sep, c)); 676 sep = "|"; 677 } 678 } 679 if (mTintMode != DEFAULT_TINT_MODE) sb.append(" mode=").append(mTintMode); 680 sb.append(")"); 681 return sb.toString(); 682 } 683 684 /** 685 * Parcelable interface 686 */ 687 public int describeContents() { 688 return (mType == TYPE_BITMAP || mType == TYPE_DATA) 689 ? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0; 690 } 691 692 // ===== Parcelable interface ====== 693 694 private Icon(Parcel in) { 695 this(in.readInt()); 696 switch (mType) { 697 case TYPE_BITMAP: 698 final Bitmap bits = Bitmap.CREATOR.createFromParcel(in); 699 mObj1 = bits; 700 break; 701 case TYPE_RESOURCE: 702 final String pkg = in.readString(); 703 final int resId = in.readInt(); 704 mString1 = pkg; 705 mInt1 = resId; 706 break; 707 case TYPE_DATA: 708 final int len = in.readInt(); 709 final byte[] a = in.readBlob(); 710 if (len != a.length) { 711 throw new RuntimeException("internal unparceling error: blob length (" 712 + a.length + ") != expected length (" + len + ")"); 713 } 714 mInt1 = len; 715 mObj1 = a; 716 break; 717 case TYPE_URI: 718 final String uri = in.readString(); 719 mString1 = uri; 720 break; 721 default: 722 throw new RuntimeException("invalid " 723 + this.getClass().getSimpleName() + " type in parcel: " + mType); 724 } 725 if (in.readInt() == 1) { 726 mTintList = ColorStateList.CREATOR.createFromParcel(in); 727 } 728 mTintMode = PorterDuff.intToMode(in.readInt()); 729 } 730 731 @Override 732 public void writeToParcel(Parcel dest, int flags) { 733 dest.writeInt(mType); 734 switch (mType) { 735 case TYPE_BITMAP: 736 final Bitmap bits = getBitmap(); 737 getBitmap().writeToParcel(dest, flags); 738 break; 739 case TYPE_RESOURCE: 740 dest.writeString(getResPackage()); 741 dest.writeInt(getResId()); 742 break; 743 case TYPE_DATA: 744 dest.writeInt(getDataLength()); 745 dest.writeBlob(getDataBytes(), getDataOffset(), getDataLength()); 746 break; 747 case TYPE_URI: 748 dest.writeString(getUriString()); 749 break; 750 } 751 if (mTintList == null) { 752 dest.writeInt(0); 753 } else { 754 dest.writeInt(1); 755 mTintList.writeToParcel(dest, flags); 756 } 757 dest.writeInt(PorterDuff.modeToInt(mTintMode)); 758 } 759 760 public static final Parcelable.Creator<Icon> CREATOR 761 = new Parcelable.Creator<Icon>() { 762 public Icon createFromParcel(Parcel in) { 763 return new Icon(in); 764 } 765 766 public Icon[] newArray(int size) { 767 return new Icon[size]; 768 } 769 }; 770 771 /** 772 * Implement this interface to receive a callback when 773 * {@link #loadDrawableAsync(Context, OnDrawableLoadedListener, Handler) loadDrawableAsync} 774 * is finished and your Drawable is ready. 775 */ 776 public interface OnDrawableLoadedListener { 777 void onDrawableLoaded(Drawable d); 778 } 779 780 /** 781 * Wrapper around loadDrawable that does its work on a pooled thread and then 782 * fires back the given (targeted) Message. 783 */ 784 private class LoadDrawableTask implements Runnable { 785 final Context mContext; 786 final Message mMessage; 787 788 public LoadDrawableTask(Context context, final Handler handler, 789 final OnDrawableLoadedListener listener) { 790 mContext = context; 791 mMessage = Message.obtain(handler, new Runnable() { 792 @Override 793 public void run() { 794 listener.onDrawableLoaded((Drawable) mMessage.obj); 795 } 796 }); 797 } 798 799 public LoadDrawableTask(Context context, Message message) { 800 mContext = context; 801 mMessage = message; 802 } 803 804 @Override 805 public void run() { 806 mMessage.obj = loadDrawable(mContext); 807 mMessage.sendToTarget(); 808 } 809 810 public void runAsync() { 811 AsyncTask.THREAD_POOL_EXECUTOR.execute(this); 812 } 813 } 814} 815