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