1214 lines
45 KiB
Java
1214 lines
45 KiB
Java
/*
|
|
* Copyright (C) 2015 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package android.graphics.drawable;
|
|
|
|
import static android.content.Context.CONTEXT_INCLUDE_CODE;
|
|
import static android.content.Context.CONTEXT_RESTRICTED;
|
|
|
|
import static com.android.graphics.flags.Flags.iconLoadDrawableReturnNullWhenUriDecodeFails;
|
|
|
|
import android.annotation.ColorInt;
|
|
import android.annotation.DrawableRes;
|
|
import android.annotation.IntDef;
|
|
import android.annotation.NonNull;
|
|
import android.annotation.Nullable;
|
|
import android.app.IUriGrantsManager;
|
|
import android.compat.annotation.UnsupportedAppUsage;
|
|
import android.content.ContentProvider;
|
|
import android.content.ContentResolver;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.pm.ApplicationInfo;
|
|
import android.content.pm.PackageManager;
|
|
import android.content.res.ColorStateList;
|
|
import android.content.res.Resources;
|
|
import android.graphics.Bitmap;
|
|
import android.graphics.BitmapFactory;
|
|
import android.graphics.BlendMode;
|
|
import android.graphics.PorterDuff;
|
|
import android.graphics.RecordingCanvas;
|
|
import android.net.Uri;
|
|
import android.os.AsyncTask;
|
|
import android.os.Build;
|
|
import android.os.Handler;
|
|
import android.os.Message;
|
|
import android.os.Parcel;
|
|
import android.os.Parcelable;
|
|
import android.os.Process;
|
|
import android.os.RemoteException;
|
|
import android.os.UserHandle;
|
|
import android.text.TextUtils;
|
|
import android.util.Log;
|
|
|
|
import androidx.annotation.RequiresPermission;
|
|
|
|
import java.io.DataInputStream;
|
|
import java.io.DataOutputStream;
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.FileNotFoundException;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.lang.annotation.Retention;
|
|
import java.lang.annotation.RetentionPolicy;
|
|
import java.util.Arrays;
|
|
import java.util.Objects;
|
|
|
|
/**
|
|
* An umbrella container for several serializable graphics representations, including Bitmaps,
|
|
* compressed bitmap images (e.g. JPG or PNG), and drawable resources (including vectors).
|
|
*
|
|
* <a href="https://developer.android.com/training/displaying-bitmaps/index.html">Much ink</a>
|
|
* has been spilled on the best way to load images, and many clients may have different needs when
|
|
* it comes to threading and fetching. This class is therefore focused on encapsulation rather than
|
|
* behavior.
|
|
*/
|
|
|
|
public final class Icon implements Parcelable {
|
|
private static final String TAG = "Icon";
|
|
private static final boolean DEBUG = false;
|
|
|
|
/**
|
|
* An icon that was created using {@link Icon#createWithBitmap(Bitmap)}.
|
|
* @see #getType
|
|
*/
|
|
public static final int TYPE_BITMAP = 1;
|
|
/**
|
|
* An icon that was created using {@link Icon#createWithResource}.
|
|
* @see #getType
|
|
*/
|
|
public static final int TYPE_RESOURCE = 2;
|
|
/**
|
|
* An icon that was created using {@link Icon#createWithData(byte[], int, int)}.
|
|
* @see #getType
|
|
*/
|
|
public static final int TYPE_DATA = 3;
|
|
/**
|
|
* An icon that was created using {@link Icon#createWithContentUri}
|
|
* or {@link Icon#createWithFilePath(String)}.
|
|
* @see #getType
|
|
*/
|
|
public static final int TYPE_URI = 4;
|
|
/**
|
|
* An icon that was created using {@link Icon#createWithAdaptiveBitmap}.
|
|
* @see #getType
|
|
*/
|
|
public static final int TYPE_ADAPTIVE_BITMAP = 5;
|
|
/**
|
|
* An icon that was created using {@link Icon#createWithAdaptiveBitmapContentUri}.
|
|
* @see #getType
|
|
*/
|
|
public static final int TYPE_URI_ADAPTIVE_BITMAP = 6;
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
@IntDef({TYPE_BITMAP, TYPE_RESOURCE, TYPE_DATA, TYPE_URI, TYPE_ADAPTIVE_BITMAP,
|
|
TYPE_URI_ADAPTIVE_BITMAP})
|
|
@Retention(RetentionPolicy.SOURCE)
|
|
public @interface IconType {
|
|
}
|
|
|
|
private static final int VERSION_STREAM_SERIALIZER = 1;
|
|
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
|
|
private final int mType;
|
|
|
|
private ColorStateList mTintList;
|
|
static final BlendMode DEFAULT_BLEND_MODE = Drawable.DEFAULT_BLEND_MODE; // SRC_IN
|
|
private BlendMode mBlendMode = Drawable.DEFAULT_BLEND_MODE;
|
|
|
|
// To avoid adding unnecessary overhead, we have a few basic objects that get repurposed
|
|
// based on the value of mType.
|
|
|
|
// TYPE_BITMAP: Bitmap
|
|
// TYPE_ADAPTIVE_BITMAP: Bitmap
|
|
// TYPE_RESOURCE: Resources
|
|
// TYPE_DATA: DataBytes
|
|
private Object mObj1;
|
|
private boolean mCachedAshmem = false;
|
|
|
|
// TYPE_RESOURCE: package name
|
|
// TYPE_URI: uri string
|
|
// TYPE_URI_ADAPTIVE_BITMAP: uri string
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
|
|
private String mString1;
|
|
|
|
// TYPE_RESOURCE: resId
|
|
// TYPE_DATA: data length
|
|
private int mInt1;
|
|
|
|
// TYPE_DATA: data offset
|
|
private int mInt2;
|
|
|
|
// TYPE_RESOURCE: use the monochrome drawable from an AdaptiveIconDrawable
|
|
private boolean mUseMonochrome = false;
|
|
|
|
// TYPE_RESOURCE: wrap the monochrome drawable in an InsetDrawable with the specified inset
|
|
private float mInsetScale = 0.0f;
|
|
|
|
/**
|
|
* Gets the type of the icon provided.
|
|
* <p>
|
|
* Note that new types may be added later, so callers should guard against other
|
|
* types being returned.
|
|
*/
|
|
@IconType
|
|
public int getType() {
|
|
return mType;
|
|
}
|
|
|
|
/**
|
|
* @return The {@link android.graphics.Bitmap} held by this {@link #TYPE_BITMAP} or
|
|
* {@link #TYPE_ADAPTIVE_BITMAP} Icon.
|
|
*
|
|
* Note that this will always return an immutable Bitmap.
|
|
* @hide
|
|
*/
|
|
@UnsupportedAppUsage
|
|
public Bitmap getBitmap() {
|
|
if (mType != TYPE_BITMAP && mType != TYPE_ADAPTIVE_BITMAP) {
|
|
throw new IllegalStateException("called getBitmap() on " + this);
|
|
}
|
|
return (Bitmap) mObj1;
|
|
}
|
|
|
|
/**
|
|
* Sets the Icon's contents to a particular Bitmap. Note that this may make a copy of the Bitmap
|
|
* if the supplied Bitmap is mutable. In that case, the value returned by getBitmap() may not
|
|
* equal the Bitmap passed to setBitmap().
|
|
*
|
|
* @hide
|
|
*/
|
|
private void setBitmap(Bitmap b) {
|
|
if (b.isMutable()) {
|
|
mObj1 = b.copy(b.getConfig(), false);
|
|
} else {
|
|
mObj1 = b;
|
|
}
|
|
mCachedAshmem = false;
|
|
}
|
|
|
|
/**
|
|
* @return The length of the compressed bitmap byte array held by this {@link #TYPE_DATA} Icon.
|
|
* @hide
|
|
*/
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
|
public int getDataLength() {
|
|
if (mType != TYPE_DATA) {
|
|
throw new IllegalStateException("called getDataLength() on " + this);
|
|
}
|
|
synchronized (this) {
|
|
return mInt1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return The offset into the byte array held by this {@link #TYPE_DATA} Icon at which
|
|
* valid compressed bitmap data is found.
|
|
* @hide
|
|
*/
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
|
|
public int getDataOffset() {
|
|
if (mType != TYPE_DATA) {
|
|
throw new IllegalStateException("called getDataOffset() on " + this);
|
|
}
|
|
synchronized (this) {
|
|
return mInt2;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return The byte array held by this {@link #TYPE_DATA} Icon ctonaining compressed
|
|
* bitmap data.
|
|
* @hide
|
|
*/
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
|
|
public byte[] getDataBytes() {
|
|
if (mType != TYPE_DATA) {
|
|
throw new IllegalStateException("called getDataBytes() on " + this);
|
|
}
|
|
synchronized (this) {
|
|
return (byte[]) mObj1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return The {@link android.content.res.Resources} for this {@link #TYPE_RESOURCE} Icon.
|
|
* @hide
|
|
*/
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
|
|
public Resources getResources() {
|
|
if (mType != TYPE_RESOURCE) {
|
|
throw new IllegalStateException("called getResources() on " + this);
|
|
}
|
|
return (Resources) mObj1;
|
|
}
|
|
|
|
/**
|
|
* Gets the package used to create this icon.
|
|
* <p>
|
|
* Only valid for icons of type {@link #TYPE_RESOURCE}.
|
|
* Note: This package may not be available if referenced in the future, and it is
|
|
* up to the caller to ensure safety if this package is re-used and/or persisted.
|
|
*/
|
|
@NonNull
|
|
public String getResPackage() {
|
|
if (mType != TYPE_RESOURCE) {
|
|
throw new IllegalStateException("called getResPackage() on " + this);
|
|
}
|
|
return mString1;
|
|
}
|
|
|
|
/**
|
|
* Gets the resource used to create this icon.
|
|
* <p>
|
|
* Only valid for icons of type {@link #TYPE_RESOURCE}.
|
|
* Note: This resource may not be available if the application changes at all, and it is
|
|
* up to the caller to ensure safety if this resource is re-used and/or persisted.
|
|
*/
|
|
@DrawableRes
|
|
public int getResId() {
|
|
if (mType != TYPE_RESOURCE) {
|
|
throw new IllegalStateException("called getResId() on " + this);
|
|
}
|
|
return mInt1;
|
|
}
|
|
|
|
/**
|
|
* @return The URI (as a String) for this {@link #TYPE_URI} or {@link #TYPE_URI_ADAPTIVE_BITMAP}
|
|
* Icon.
|
|
* @hide
|
|
*/
|
|
public String getUriString() {
|
|
if (mType != TYPE_URI && mType != TYPE_URI_ADAPTIVE_BITMAP) {
|
|
throw new IllegalStateException("called getUriString() on " + this);
|
|
}
|
|
return mString1;
|
|
}
|
|
|
|
/**
|
|
* Gets the uri used to create this icon.
|
|
* <p>
|
|
* Only valid for icons of type {@link #TYPE_URI} and {@link #TYPE_URI_ADAPTIVE_BITMAP}.
|
|
* Note: This uri may not be available in the future, and it is
|
|
* up to the caller to ensure safety if this uri is re-used and/or persisted.
|
|
*/
|
|
@NonNull
|
|
public Uri getUri() {
|
|
return Uri.parse(getUriString());
|
|
}
|
|
|
|
private static final String typeToString(int x) {
|
|
switch (x) {
|
|
case TYPE_BITMAP: return "BITMAP";
|
|
case TYPE_ADAPTIVE_BITMAP: return "BITMAP_MASKABLE";
|
|
case TYPE_DATA: return "DATA";
|
|
case TYPE_RESOURCE: return "RESOURCE";
|
|
case TYPE_URI: return "URI";
|
|
case TYPE_URI_ADAPTIVE_BITMAP: return "URI_MASKABLE";
|
|
default: return "UNKNOWN";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invokes {@link #loadDrawable(Context)} on the given {@link android.os.Handler Handler}
|
|
* and then sends <code>andThen</code> to the same Handler when finished.
|
|
*
|
|
* @param context {@link android.content.Context Context} in which to load the drawable; see
|
|
* {@link #loadDrawable(Context)}
|
|
* @param andThen {@link android.os.Message} to send to its target once the drawable
|
|
* is available. The {@link android.os.Message#obj obj}
|
|
* property is populated with the Drawable.
|
|
*/
|
|
public void loadDrawableAsync(@NonNull Context context, @NonNull Message andThen) {
|
|
if (andThen.getTarget() == null) {
|
|
throw new IllegalArgumentException("callback message must have a target handler");
|
|
}
|
|
new LoadDrawableTask(context, andThen).runAsync();
|
|
}
|
|
|
|
/**
|
|
* Invokes {@link #loadDrawable(Context)} on a background thread and notifies the <code>
|
|
* {@link OnDrawableLoadedListener#onDrawableLoaded listener} </code> on the {@code handler}
|
|
* when finished.
|
|
*
|
|
* @param context {@link Context Context} in which to load the drawable; see
|
|
* {@link #loadDrawable(Context)}
|
|
* @param listener to be {@link OnDrawableLoadedListener#onDrawableLoaded notified} when
|
|
* {@link #loadDrawable(Context)} finished
|
|
* @param handler {@link Handler} on which to notify the {@code listener}
|
|
*/
|
|
public void loadDrawableAsync(@NonNull Context context, final OnDrawableLoadedListener listener,
|
|
Handler handler) {
|
|
new LoadDrawableTask(context, handler, listener).runAsync();
|
|
}
|
|
|
|
/**
|
|
* Returns a Drawable that can be used to draw the image inside this Icon, constructing it
|
|
* if necessary. Depending on the type of image, this may not be something you want to do on
|
|
* the UI thread, so consider using
|
|
* {@link #loadDrawableAsync(Context, Message) loadDrawableAsync} instead.
|
|
*
|
|
* @param context {@link android.content.Context Context} in which to load the drawable; used
|
|
* to access {@link android.content.res.Resources Resources}, for example.
|
|
* @return A fresh instance of a drawable for this image, yours to keep.
|
|
*/
|
|
public @Nullable Drawable loadDrawable(Context context) {
|
|
final Drawable result = loadDrawableInner(context);
|
|
if (result != null && hasTint()) {
|
|
result.mutate();
|
|
result.setTintList(mTintList);
|
|
result.setTintBlendMode(mBlendMode);
|
|
}
|
|
|
|
if (mUseMonochrome) {
|
|
return crateMonochromeDrawable(result, mInsetScale);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Gets the monochrome drawable from an {@link AdaptiveIconDrawable}.
|
|
*
|
|
* @param drawable An {@link AdaptiveIconDrawable}
|
|
* @return Adjusted (wrapped in {@link InsetDrawable}) monochrome drawable
|
|
* from an {@link AdaptiveIconDrawable}.
|
|
* Or the original drawable if no monochrome layer exists.
|
|
*/
|
|
private static Drawable crateMonochromeDrawable(Drawable drawable, float inset) {
|
|
if (drawable instanceof AdaptiveIconDrawable) {
|
|
Drawable monochromeDrawable = ((AdaptiveIconDrawable) drawable).getMonochrome();
|
|
// wrap with negative inset => scale icon (inspired from BaseIconFactory)
|
|
if (monochromeDrawable != null) {
|
|
return new InsetDrawable(monochromeDrawable, inset);
|
|
}
|
|
}
|
|
return drawable;
|
|
}
|
|
|
|
/**
|
|
* Resizes image if size too large for Canvas to draw
|
|
* @param bitmap Bitmap to be resized if size > {@link RecordingCanvas.MAX_BITMAP_SIZE}
|
|
* @return resized bitmap
|
|
*/
|
|
private Bitmap fixMaxBitmapSize(Bitmap bitmap) {
|
|
if (bitmap != null && bitmap.getByteCount() > RecordingCanvas.MAX_BITMAP_SIZE) {
|
|
int bytesPerPixel = bitmap.getRowBytes() / bitmap.getWidth();
|
|
int maxNumPixels = RecordingCanvas.MAX_BITMAP_SIZE / bytesPerPixel;
|
|
float aspRatio = (float) bitmap.getWidth() / (float) bitmap.getHeight();
|
|
int newHeight = (int) Math.sqrt(maxNumPixels / aspRatio);
|
|
int newWidth = (int) (newHeight * aspRatio);
|
|
|
|
if (DEBUG) {
|
|
Log.d(TAG,
|
|
"Image size too large: " + bitmap.getByteCount() + ". Resizing bitmap to: "
|
|
+ newWidth + " " + newHeight);
|
|
}
|
|
|
|
return scaleDownIfNecessary(bitmap, newWidth, newHeight);
|
|
}
|
|
return bitmap;
|
|
}
|
|
|
|
/**
|
|
* Resizes BitmapDrawable if size too large for Canvas to draw
|
|
* @param drawable Drawable to be resized if size > {@link RecordingCanvas.MAX_BITMAP_SIZE}
|
|
* @return resized Drawable
|
|
*/
|
|
private Drawable fixMaxBitmapSize(Resources res, Drawable drawable) {
|
|
if (drawable instanceof BitmapDrawable) {
|
|
Bitmap scaledBmp = fixMaxBitmapSize(((BitmapDrawable) drawable).getBitmap());
|
|
return new BitmapDrawable(res, scaledBmp);
|
|
}
|
|
return drawable;
|
|
}
|
|
|
|
/**
|
|
* Do the heavy lifting of loading the drawable, but stop short of applying any tint.
|
|
*/
|
|
private Drawable loadDrawableInner(Context context) {
|
|
switch (mType) {
|
|
case TYPE_BITMAP:
|
|
return new BitmapDrawable(context.getResources(), fixMaxBitmapSize(getBitmap()));
|
|
case TYPE_ADAPTIVE_BITMAP:
|
|
return new AdaptiveIconDrawable(null,
|
|
new BitmapDrawable(context.getResources(), fixMaxBitmapSize(getBitmap())));
|
|
case TYPE_RESOURCE:
|
|
if (getResources() == null) {
|
|
// figure out where to load resources from
|
|
String resPackage = getResPackage();
|
|
if (TextUtils.isEmpty(resPackage)) {
|
|
// if none is specified, try the given context
|
|
resPackage = context.getPackageName();
|
|
}
|
|
if ("android".equals(resPackage)) {
|
|
mObj1 = Resources.getSystem();
|
|
} else {
|
|
final PackageManager pm = context.getPackageManager();
|
|
try {
|
|
ApplicationInfo ai = pm.getApplicationInfo(
|
|
resPackage,
|
|
PackageManager.MATCH_UNINSTALLED_PACKAGES
|
|
| PackageManager.GET_SHARED_LIBRARY_FILES);
|
|
if (ai != null) {
|
|
mObj1 = pm.getResourcesForApplication(ai);
|
|
} else {
|
|
break;
|
|
}
|
|
} catch (PackageManager.NameNotFoundException e) {
|
|
Log.e(TAG, String.format("Unable to find pkg=%s for icon %s",
|
|
resPackage, this), e);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
try {
|
|
return fixMaxBitmapSize(getResources(),
|
|
getResources().getDrawable(getResId(), context.getTheme()));
|
|
} catch (RuntimeException e) {
|
|
Log.e(TAG, String.format("Unable to load resource 0x%08x from pkg=%s",
|
|
getResId(),
|
|
getResPackage()),
|
|
e);
|
|
}
|
|
break;
|
|
case TYPE_DATA:
|
|
return new BitmapDrawable(context.getResources(), fixMaxBitmapSize(
|
|
BitmapFactory.decodeByteArray(getDataBytes(), getDataOffset(),
|
|
getDataLength())));
|
|
case TYPE_URI:
|
|
InputStream is = getUriInputStream(context);
|
|
if (is != null) {
|
|
final Bitmap bitmap = BitmapFactory.decodeStream(is);
|
|
if (bitmap == null) {
|
|
Log.w(TAG, "Unable to decode image from URI: " + getUriString());
|
|
if (iconLoadDrawableReturnNullWhenUriDecodeFails()) {
|
|
return null;
|
|
}
|
|
}
|
|
return new BitmapDrawable(context.getResources(), fixMaxBitmapSize(bitmap));
|
|
}
|
|
break;
|
|
case TYPE_URI_ADAPTIVE_BITMAP:
|
|
is = getUriInputStream(context);
|
|
if (is != null) {
|
|
final Bitmap bitmap = BitmapFactory.decodeStream(is);
|
|
if (bitmap == null) {
|
|
Log.w(TAG, "Unable to decode image from URI: " + getUriString());
|
|
if (iconLoadDrawableReturnNullWhenUriDecodeFails()) {
|
|
return null;
|
|
}
|
|
}
|
|
return new AdaptiveIconDrawable(null, new BitmapDrawable(context.getResources(),
|
|
fixMaxBitmapSize(bitmap)));
|
|
}
|
|
break;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private @Nullable InputStream getUriInputStream(Context context) {
|
|
final Uri uri = getUri();
|
|
final String scheme = uri.getScheme();
|
|
if (ContentResolver.SCHEME_CONTENT.equals(scheme)
|
|
|| ContentResolver.SCHEME_FILE.equals(scheme)) {
|
|
try {
|
|
return context.getContentResolver().openInputStream(uri);
|
|
} catch (Exception e) {
|
|
Log.w(TAG, "Unable to load image from URI: " + uri, e);
|
|
}
|
|
} else {
|
|
try {
|
|
return new FileInputStream(new File(mString1));
|
|
} catch (FileNotFoundException e) {
|
|
Log.w(TAG, "Unable to load image from path: " + uri, e);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Load the requested resources under the given userId, if the system allows it,
|
|
* before actually loading the drawable.
|
|
*
|
|
* @hide
|
|
*/
|
|
public Drawable loadDrawableAsUser(Context context, int userId) {
|
|
if (mType == TYPE_RESOURCE) {
|
|
String resPackage = getResPackage();
|
|
if (TextUtils.isEmpty(resPackage)) {
|
|
resPackage = context.getPackageName();
|
|
}
|
|
if (getResources() == null && !(getResPackage().equals("android"))) {
|
|
// TODO(b/173307037): Move CONTEXT_INCLUDE_CODE to ContextImpl.createContextAsUser
|
|
final Context userContext;
|
|
if (context.getUserId() == userId) {
|
|
userContext = context;
|
|
} else {
|
|
final boolean sameAppWithProcess =
|
|
UserHandle.isSameApp(context.getApplicationInfo().uid, Process.myUid());
|
|
final int flags = (sameAppWithProcess ? CONTEXT_INCLUDE_CODE : 0)
|
|
| CONTEXT_RESTRICTED;
|
|
userContext = context.createContextAsUser(UserHandle.of(userId), flags);
|
|
}
|
|
|
|
final PackageManager pm = userContext.getPackageManager();
|
|
try {
|
|
// assign getResources() as the correct user
|
|
mObj1 = pm.getResourcesForApplication(resPackage);
|
|
} catch (PackageManager.NameNotFoundException e) {
|
|
Log.e(TAG, String.format("Unable to find pkg=%s user=%d",
|
|
getResPackage(),
|
|
userId),
|
|
e);
|
|
}
|
|
}
|
|
}
|
|
return loadDrawable(context);
|
|
}
|
|
|
|
/**
|
|
* Load a drawable, but in the case of URI types, it will check if the passed uid has a grant
|
|
* to load the resource. The check will be performed using the permissions of the passed uid,
|
|
* and not those of the caller.
|
|
* <p>
|
|
* This should be called for {@link Icon} objects that come from a not trusted source and may
|
|
* contain a URI.
|
|
*
|
|
* After the check, if passed, {@link #loadDrawable} will be called. If failed, this will
|
|
* return {@code null}.
|
|
*
|
|
* @see #loadDrawable
|
|
*
|
|
* @hide
|
|
*/
|
|
@Nullable
|
|
@RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
|
|
public Drawable loadDrawableCheckingUriGrant(
|
|
Context context,
|
|
IUriGrantsManager iugm,
|
|
int callingUid,
|
|
String packageName
|
|
) {
|
|
if (getType() == TYPE_URI || getType() == TYPE_URI_ADAPTIVE_BITMAP) {
|
|
try {
|
|
iugm.checkGrantUriPermission_ignoreNonSystem(
|
|
callingUid,
|
|
packageName,
|
|
ContentProvider.getUriWithoutUserId(getUri()),
|
|
Intent.FLAG_GRANT_READ_URI_PERMISSION,
|
|
ContentProvider.getUserIdFromUri(getUri())
|
|
);
|
|
} catch (SecurityException | RemoteException e) {
|
|
Log.e(TAG, "Failed to get URI permission for: " + getUri(), e);
|
|
return null;
|
|
}
|
|
}
|
|
return loadDrawable(context);
|
|
}
|
|
|
|
/** @hide */
|
|
public static final int MIN_ASHMEM_ICON_SIZE = 128 * (1 << 10);
|
|
|
|
/**
|
|
* Puts the memory used by this instance into Ashmem memory, if possible.
|
|
* @hide
|
|
*/
|
|
public void convertToAshmem() {
|
|
if ((mType == TYPE_BITMAP || mType == TYPE_ADAPTIVE_BITMAP) &&
|
|
getBitmap().isMutable() &&
|
|
getBitmap().getAllocationByteCount() >= MIN_ASHMEM_ICON_SIZE) {
|
|
setBitmap(getBitmap().asShared());
|
|
}
|
|
mCachedAshmem = true;
|
|
}
|
|
|
|
/**
|
|
* Writes a serialized version of an Icon to the specified stream.
|
|
*
|
|
* @param stream The stream on which to serialize the Icon.
|
|
* @hide
|
|
*/
|
|
public void writeToStream(@NonNull OutputStream stream) throws IOException {
|
|
DataOutputStream dataStream = new DataOutputStream(stream);
|
|
|
|
dataStream.writeInt(VERSION_STREAM_SERIALIZER);
|
|
dataStream.writeByte(mType);
|
|
|
|
switch (mType) {
|
|
case TYPE_BITMAP:
|
|
case TYPE_ADAPTIVE_BITMAP:
|
|
getBitmap().compress(Bitmap.CompressFormat.PNG, 100, dataStream);
|
|
break;
|
|
case TYPE_DATA:
|
|
dataStream.writeInt(getDataLength());
|
|
dataStream.write(getDataBytes(), getDataOffset(), getDataLength());
|
|
break;
|
|
case TYPE_RESOURCE:
|
|
dataStream.writeUTF(getResPackage());
|
|
dataStream.writeInt(getResId());
|
|
break;
|
|
case TYPE_URI:
|
|
case TYPE_URI_ADAPTIVE_BITMAP:
|
|
dataStream.writeUTF(getUriString());
|
|
break;
|
|
}
|
|
}
|
|
|
|
private Icon(int mType) {
|
|
this.mType = mType;
|
|
}
|
|
|
|
/**
|
|
* Create an Icon from the specified stream.
|
|
*
|
|
* @param stream The input stream from which to reconstruct the Icon.
|
|
* @hide
|
|
*/
|
|
public static @Nullable Icon createFromStream(@NonNull InputStream stream) throws IOException {
|
|
DataInputStream inputStream = new DataInputStream(stream);
|
|
|
|
final int version = inputStream.readInt();
|
|
if (version >= VERSION_STREAM_SERIALIZER) {
|
|
final int type = inputStream.readByte();
|
|
switch (type) {
|
|
case TYPE_BITMAP:
|
|
return createWithBitmap(BitmapFactory.decodeStream(inputStream));
|
|
case TYPE_ADAPTIVE_BITMAP:
|
|
return createWithAdaptiveBitmap(BitmapFactory.decodeStream(inputStream));
|
|
case TYPE_DATA:
|
|
final int length = inputStream.readInt();
|
|
final byte[] data = new byte[length];
|
|
inputStream.read(data, 0 /* offset */, length);
|
|
return createWithData(data, 0 /* offset */, length);
|
|
case TYPE_RESOURCE:
|
|
final String packageName = inputStream.readUTF();
|
|
final int resId = inputStream.readInt();
|
|
return createWithResource(packageName, resId);
|
|
case TYPE_URI:
|
|
final String uriOrPath = inputStream.readUTF();
|
|
return createWithContentUri(uriOrPath);
|
|
case TYPE_URI_ADAPTIVE_BITMAP:
|
|
final String uri = inputStream.readUTF();
|
|
return createWithAdaptiveBitmapContentUri(uri);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Compares if this icon is constructed from the same resources as another icon.
|
|
* Note that this is an inexpensive operation and doesn't do deep Bitmap equality comparisons.
|
|
*
|
|
* @param otherIcon the other icon
|
|
* @return whether this icon is the same as the another one
|
|
* @hide
|
|
*/
|
|
public boolean sameAs(@NonNull Icon otherIcon) {
|
|
if (otherIcon == this) {
|
|
return true;
|
|
}
|
|
if (mType != otherIcon.getType()) {
|
|
return false;
|
|
}
|
|
switch (mType) {
|
|
case TYPE_BITMAP:
|
|
case TYPE_ADAPTIVE_BITMAP:
|
|
return getBitmap() == otherIcon.getBitmap();
|
|
case TYPE_DATA:
|
|
return getDataLength() == otherIcon.getDataLength()
|
|
&& getDataOffset() == otherIcon.getDataOffset()
|
|
&& Arrays.equals(getDataBytes(), otherIcon.getDataBytes());
|
|
case TYPE_RESOURCE:
|
|
return getResId() == otherIcon.getResId()
|
|
&& Objects.equals(getResPackage(), otherIcon.getResPackage())
|
|
&& mUseMonochrome == otherIcon.mUseMonochrome
|
|
&& mInsetScale == otherIcon.mInsetScale;
|
|
case TYPE_URI:
|
|
case TYPE_URI_ADAPTIVE_BITMAP:
|
|
return Objects.equals(getUriString(), otherIcon.getUriString());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Create an Icon pointing to a drawable resource.
|
|
* @param context The context for the application whose resources should be used to resolve the
|
|
* given resource ID.
|
|
* @param resId ID of the drawable resource
|
|
*/
|
|
public static @NonNull Icon createWithResource(Context context, @DrawableRes int resId) {
|
|
if (context == null) {
|
|
throw new IllegalArgumentException("Context must not be null.");
|
|
}
|
|
final Icon rep = new Icon(TYPE_RESOURCE);
|
|
rep.mInt1 = resId;
|
|
rep.mString1 = context.getPackageName();
|
|
return rep;
|
|
}
|
|
|
|
/**
|
|
* Version of createWithResource that takes Resources. Do not use.
|
|
* @hide
|
|
*/
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
|
public static @NonNull Icon createWithResource(Resources res, @DrawableRes int resId) {
|
|
if (res == null) {
|
|
throw new IllegalArgumentException("Resource must not be null.");
|
|
}
|
|
final Icon rep = new Icon(TYPE_RESOURCE);
|
|
rep.mInt1 = resId;
|
|
rep.mString1 = res.getResourcePackageName(resId);
|
|
return rep;
|
|
}
|
|
|
|
/**
|
|
* Create an Icon pointing to a drawable resource.
|
|
* @param resPackage Name of the package containing the resource in question
|
|
* @param resId ID of the drawable resource
|
|
*/
|
|
public static @NonNull Icon createWithResource(String resPackage, @DrawableRes int resId) {
|
|
if (resPackage == null) {
|
|
throw new IllegalArgumentException("Resource package name must not be null.");
|
|
}
|
|
final Icon rep = new Icon(TYPE_RESOURCE);
|
|
rep.mInt1 = resId;
|
|
rep.mString1 = resPackage;
|
|
return rep;
|
|
}
|
|
|
|
/**
|
|
* Create an Icon pointing to a drawable resource.
|
|
* @param resPackage Name of the package containing the resource in question
|
|
* @param resId ID of the drawable resource
|
|
* @param useMonochrome if this icon should use the monochrome res from the adaptive drawable
|
|
* @hide
|
|
*/
|
|
public static @NonNull Icon createWithResourceAdaptiveDrawable(@NonNull String resPackage,
|
|
@DrawableRes int resId, boolean useMonochrome, float inset) {
|
|
if (resPackage == null) {
|
|
throw new IllegalArgumentException("Resource package name must not be null.");
|
|
}
|
|
final Icon rep = new Icon(TYPE_RESOURCE);
|
|
rep.mInt1 = resId;
|
|
rep.mUseMonochrome = useMonochrome;
|
|
rep.mInsetScale = inset;
|
|
rep.mString1 = resPackage;
|
|
return rep;
|
|
}
|
|
|
|
/**
|
|
* Create an Icon pointing to a bitmap in memory.
|
|
* @param bits A valid {@link android.graphics.Bitmap} object
|
|
*/
|
|
public static @NonNull Icon createWithBitmap(Bitmap bits) {
|
|
if (bits == null) {
|
|
throw new IllegalArgumentException("Bitmap must not be null.");
|
|
}
|
|
final Icon rep = new Icon(TYPE_BITMAP);
|
|
rep.setBitmap(bits);
|
|
return rep;
|
|
}
|
|
|
|
/**
|
|
* Create an Icon pointing to a bitmap in memory that follows the icon design guideline defined
|
|
* by {@link AdaptiveIconDrawable}.
|
|
* @param bits A valid {@link android.graphics.Bitmap} object
|
|
*/
|
|
public static @NonNull Icon createWithAdaptiveBitmap(Bitmap bits) {
|
|
if (bits == null) {
|
|
throw new IllegalArgumentException("Bitmap must not be null.");
|
|
}
|
|
final Icon rep = new Icon(TYPE_ADAPTIVE_BITMAP);
|
|
rep.setBitmap(bits);
|
|
return rep;
|
|
}
|
|
|
|
/**
|
|
* Create an Icon pointing to a compressed bitmap stored in a byte array.
|
|
* @param data Byte array storing compressed bitmap data of a type that
|
|
* {@link android.graphics.BitmapFactory}
|
|
* can decode (see {@link android.graphics.Bitmap.CompressFormat}).
|
|
* @param offset Offset into <code>data</code> at which the bitmap data starts
|
|
* @param length Length of the bitmap data
|
|
*/
|
|
public static @NonNull Icon createWithData(byte[] data, int offset, int length) {
|
|
if (data == null) {
|
|
throw new IllegalArgumentException("Data must not be null.");
|
|
}
|
|
final Icon rep = new Icon(TYPE_DATA);
|
|
rep.mObj1 = data;
|
|
rep.mInt1 = length;
|
|
rep.mInt2 = offset;
|
|
return rep;
|
|
}
|
|
|
|
/**
|
|
* Create an Icon pointing to an image file specified by URI.
|
|
*
|
|
* @param uri A uri referring to local content:// or file:// image data.
|
|
*/
|
|
public static @NonNull Icon createWithContentUri(String uri) {
|
|
if (uri == null) {
|
|
throw new IllegalArgumentException("Uri must not be null.");
|
|
}
|
|
final Icon rep = new Icon(TYPE_URI);
|
|
rep.mString1 = uri;
|
|
return rep;
|
|
}
|
|
|
|
/**
|
|
* Create an Icon pointing to an image file specified by URI.
|
|
*
|
|
* @param uri A uri referring to local content:// or file:// image data.
|
|
*/
|
|
public static @NonNull Icon createWithContentUri(Uri uri) {
|
|
if (uri == null) {
|
|
throw new IllegalArgumentException("Uri must not be null.");
|
|
}
|
|
return createWithContentUri(uri.toString());
|
|
}
|
|
|
|
/**
|
|
* Create an Icon pointing to an image file specified by URI. Image file should follow the icon
|
|
* design guideline defined by {@link AdaptiveIconDrawable}.
|
|
*
|
|
* @param uri A uri referring to local content:// or file:// image data.
|
|
*/
|
|
public static @NonNull Icon createWithAdaptiveBitmapContentUri(@NonNull String uri) {
|
|
if (uri == null) {
|
|
throw new IllegalArgumentException("Uri must not be null.");
|
|
}
|
|
final Icon rep = new Icon(TYPE_URI_ADAPTIVE_BITMAP);
|
|
rep.mString1 = uri;
|
|
return rep;
|
|
}
|
|
|
|
/**
|
|
* Create an Icon pointing to an image file specified by URI. Image file should follow the icon
|
|
* design guideline defined by {@link AdaptiveIconDrawable}.
|
|
*
|
|
* @param uri A uri referring to local content:// or file:// image data.
|
|
*/
|
|
@NonNull
|
|
public static Icon createWithAdaptiveBitmapContentUri(@NonNull Uri uri) {
|
|
if (uri == null) {
|
|
throw new IllegalArgumentException("Uri must not be null.");
|
|
}
|
|
return createWithAdaptiveBitmapContentUri(uri.toString());
|
|
}
|
|
|
|
/**
|
|
* Store a color to use whenever this Icon is drawn.
|
|
*
|
|
* @param tint a color, as in {@link Drawable#setTint(int)}
|
|
* @return this same object, for use in chained construction
|
|
*/
|
|
public @NonNull Icon setTint(@ColorInt int tint) {
|
|
return setTintList(ColorStateList.valueOf(tint));
|
|
}
|
|
|
|
/**
|
|
* Store a color to use whenever this Icon is drawn.
|
|
*
|
|
* @param tintList as in {@link Drawable#setTintList(ColorStateList)}, null to remove tint
|
|
* @return this same object, for use in chained construction
|
|
*/
|
|
public @NonNull Icon setTintList(ColorStateList tintList) {
|
|
mTintList = tintList;
|
|
return this;
|
|
}
|
|
|
|
/** @hide */
|
|
public @Nullable ColorStateList getTintList() {
|
|
return mTintList;
|
|
}
|
|
|
|
/**
|
|
* Store a blending mode to use whenever this Icon is drawn.
|
|
*
|
|
* @param mode a blending mode, as in {@link Drawable#setTintMode(PorterDuff.Mode)}, may be null
|
|
* @return this same object, for use in chained construction
|
|
*/
|
|
public @NonNull Icon setTintMode(@NonNull PorterDuff.Mode mode) {
|
|
mBlendMode = BlendMode.fromValue(mode.nativeInt);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Store a blending mode to use whenever this Icon is drawn.
|
|
*
|
|
* @param mode a blending mode, as in {@link Drawable#setTintMode(PorterDuff.Mode)}, may be null
|
|
* @return this same object, for use in chained construction
|
|
*/
|
|
public @NonNull Icon setTintBlendMode(@NonNull BlendMode mode) {
|
|
mBlendMode = mode;
|
|
return this;
|
|
}
|
|
|
|
/** @hide */
|
|
public @NonNull BlendMode getTintBlendMode() {
|
|
return mBlendMode;
|
|
}
|
|
|
|
/** @hide */
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
|
public boolean hasTint() {
|
|
return (mTintList != null) || (mBlendMode != DEFAULT_BLEND_MODE);
|
|
}
|
|
|
|
/**
|
|
* Create an Icon pointing to an image file specified by path.
|
|
*
|
|
* @param path A path to a file that contains compressed bitmap data of
|
|
* a type that {@link android.graphics.BitmapFactory} can decode.
|
|
*/
|
|
public static @NonNull Icon createWithFilePath(String path) {
|
|
if (path == null) {
|
|
throw new IllegalArgumentException("Path must not be null.");
|
|
}
|
|
final Icon rep = new Icon(TYPE_URI);
|
|
rep.mString1 = path;
|
|
return rep;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
final StringBuilder sb = new StringBuilder("Icon(typ=").append(typeToString(mType));
|
|
switch (mType) {
|
|
case TYPE_BITMAP:
|
|
case TYPE_ADAPTIVE_BITMAP:
|
|
sb.append(" size=")
|
|
.append(getBitmap().getWidth())
|
|
.append("x")
|
|
.append(getBitmap().getHeight());
|
|
break;
|
|
case TYPE_RESOURCE:
|
|
sb.append(" pkg=")
|
|
.append(getResPackage())
|
|
.append(" id=")
|
|
.append(String.format("0x%08x", getResId()));
|
|
break;
|
|
case TYPE_DATA:
|
|
sb.append(" len=").append(getDataLength());
|
|
if (getDataOffset() != 0) {
|
|
sb.append(" off=").append(getDataOffset());
|
|
}
|
|
break;
|
|
case TYPE_URI:
|
|
case TYPE_URI_ADAPTIVE_BITMAP:
|
|
sb.append(" uri=").append(getUriString());
|
|
break;
|
|
}
|
|
if (mTintList != null) {
|
|
sb.append(" tint=");
|
|
String sep = "";
|
|
for (int c : mTintList.getColors()) {
|
|
sb.append(String.format("%s0x%08x", sep, c));
|
|
sep = "|";
|
|
}
|
|
}
|
|
if (mBlendMode != DEFAULT_BLEND_MODE) sb.append(" mode=").append(mBlendMode);
|
|
sb.append(")");
|
|
return sb.toString();
|
|
}
|
|
|
|
/**
|
|
* Parcelable interface
|
|
*/
|
|
public int describeContents() {
|
|
return (mType == TYPE_BITMAP || mType == TYPE_ADAPTIVE_BITMAP || mType == TYPE_DATA)
|
|
? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0;
|
|
}
|
|
|
|
// ===== Parcelable interface ======
|
|
|
|
private Icon(Parcel in) {
|
|
this(in.readInt());
|
|
switch (mType) {
|
|
case TYPE_BITMAP:
|
|
case TYPE_ADAPTIVE_BITMAP:
|
|
final Bitmap bits = Bitmap.CREATOR.createFromParcel(in);
|
|
mObj1 = bits;
|
|
break;
|
|
case TYPE_RESOURCE:
|
|
final String pkg = in.readString();
|
|
final int resId = in.readInt();
|
|
mString1 = pkg;
|
|
mInt1 = resId;
|
|
mUseMonochrome = in.readBoolean();
|
|
mInsetScale = in.readFloat();
|
|
break;
|
|
case TYPE_DATA:
|
|
final int len = in.readInt();
|
|
final byte[] a = in.readBlob();
|
|
if (len != a.length) {
|
|
throw new RuntimeException("internal unparceling error: blob length ("
|
|
+ a.length + ") != expected length (" + len + ")");
|
|
}
|
|
mInt1 = len;
|
|
mObj1 = a;
|
|
break;
|
|
case TYPE_URI:
|
|
case TYPE_URI_ADAPTIVE_BITMAP:
|
|
final String uri = in.readString();
|
|
mString1 = uri;
|
|
break;
|
|
default:
|
|
throw new RuntimeException("invalid "
|
|
+ this.getClass().getSimpleName() + " type in parcel: " + mType);
|
|
}
|
|
if (in.readInt() == 1) {
|
|
mTintList = ColorStateList.CREATOR.createFromParcel(in);
|
|
}
|
|
mBlendMode = BlendMode.fromValue(in.readInt());
|
|
}
|
|
|
|
@Override
|
|
public void writeToParcel(Parcel dest, int flags) {
|
|
dest.writeInt(mType);
|
|
switch (mType) {
|
|
case TYPE_BITMAP:
|
|
case TYPE_ADAPTIVE_BITMAP:
|
|
if (!mCachedAshmem) {
|
|
mObj1 = ((Bitmap) mObj1).asShared();
|
|
mCachedAshmem = true;
|
|
}
|
|
getBitmap().writeToParcel(dest, flags);
|
|
break;
|
|
case TYPE_RESOURCE:
|
|
dest.writeString(getResPackage());
|
|
dest.writeInt(getResId());
|
|
dest.writeBoolean(mUseMonochrome);
|
|
dest.writeFloat(mInsetScale);
|
|
break;
|
|
case TYPE_DATA:
|
|
dest.writeInt(getDataLength());
|
|
dest.writeBlob(getDataBytes(), getDataOffset(), getDataLength());
|
|
break;
|
|
case TYPE_URI:
|
|
case TYPE_URI_ADAPTIVE_BITMAP:
|
|
dest.writeString(getUriString());
|
|
break;
|
|
}
|
|
if (mTintList == null) {
|
|
dest.writeInt(0);
|
|
} else {
|
|
dest.writeInt(1);
|
|
mTintList.writeToParcel(dest, flags);
|
|
}
|
|
dest.writeInt(BlendMode.toValue(mBlendMode));
|
|
}
|
|
|
|
public static final @android.annotation.NonNull Parcelable.Creator<Icon> CREATOR
|
|
= new Parcelable.Creator<Icon>() {
|
|
public Icon createFromParcel(Parcel in) {
|
|
return new Icon(in);
|
|
}
|
|
|
|
public Icon[] newArray(int size) {
|
|
return new Icon[size];
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Scale down a bitmap to a given max width and max height. The scaling will be done in a uniform way
|
|
* @param bitmap the bitmap to scale down
|
|
* @param maxWidth the maximum width allowed
|
|
* @param maxHeight the maximum height allowed
|
|
*
|
|
* @return the scaled bitmap if necessary or the original bitmap if no scaling was needed
|
|
* @hide
|
|
*/
|
|
public static Bitmap scaleDownIfNecessary(Bitmap bitmap, int maxWidth, int maxHeight) {
|
|
int bitmapWidth = bitmap.getWidth();
|
|
int bitmapHeight = bitmap.getHeight();
|
|
if (bitmapWidth > maxWidth || bitmapHeight > maxHeight) {
|
|
float scale = Math.min((float) maxWidth / bitmapWidth,
|
|
(float) maxHeight / bitmapHeight);
|
|
bitmap = Bitmap.createScaledBitmap(bitmap,
|
|
Math.max(1, (int) (scale * bitmapWidth)),
|
|
Math.max(1, (int) (scale * bitmapHeight)),
|
|
true /* filter */);
|
|
}
|
|
return bitmap;
|
|
}
|
|
|
|
/**
|
|
* Scale down this icon to a given max width and max height.
|
|
* The scaling will be done in a uniform way and currently only bitmaps are supported.
|
|
* @param maxWidth the maximum width allowed
|
|
* @param maxHeight the maximum height allowed
|
|
*
|
|
* @hide
|
|
*/
|
|
public void scaleDownIfNecessary(int maxWidth, int maxHeight) {
|
|
if (mType != TYPE_BITMAP && mType != TYPE_ADAPTIVE_BITMAP) {
|
|
return;
|
|
}
|
|
Bitmap bitmap = getBitmap();
|
|
setBitmap(scaleDownIfNecessary(bitmap, maxWidth, maxHeight));
|
|
}
|
|
|
|
/**
|
|
* Implement this interface to receive a callback when
|
|
* {@link #loadDrawableAsync(Context, OnDrawableLoadedListener, Handler) loadDrawableAsync}
|
|
* is finished and your Drawable is ready.
|
|
*/
|
|
public interface OnDrawableLoadedListener {
|
|
void onDrawableLoaded(Drawable d);
|
|
}
|
|
|
|
/**
|
|
* Wrapper around loadDrawable that does its work on a pooled thread and then
|
|
* fires back the given (targeted) Message.
|
|
*/
|
|
private class LoadDrawableTask implements Runnable {
|
|
final Context mContext;
|
|
final Message mMessage;
|
|
|
|
public LoadDrawableTask(Context context, final Handler handler,
|
|
final OnDrawableLoadedListener listener) {
|
|
mContext = context;
|
|
mMessage = Message.obtain(handler, new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
listener.onDrawableLoaded((Drawable) mMessage.obj);
|
|
}
|
|
});
|
|
}
|
|
|
|
public LoadDrawableTask(Context context, Message message) {
|
|
mContext = context;
|
|
mMessage = message;
|
|
}
|
|
|
|
@Override
|
|
public void run() {
|
|
mMessage.obj = loadDrawable(mContext);
|
|
mMessage.sendToTarget();
|
|
}
|
|
|
|
public void runAsync() {
|
|
AsyncTask.THREAD_POOL_EXECUTOR.execute(this);
|
|
}
|
|
}
|
|
}
|