From b9f7aac3488873677377b36c57338d758098f78e Mon Sep 17 00:00:00 2001 From: Dan Sandler Date: Wed, 4 Mar 2015 13:08:49 -0500 Subject: [PATCH] Icon: a clean, parcelable place for images. Binder APIs which wish to consume Bitmaps *and* drawable resources can now do so by using Icon, a kind of union type that accommodates each of these. Icon also accepts byte arrays holding compressed Bitmaps (PNG, JPEG, etc), which saves clients the additional memory cost of decoding and sending full uncompressed bitmaps through Binder interfaces. Receiving clients can call loadDrawable{,Async} and then getDrawable to start immediately using the image in an ImageView or other Drawable-hosting container. Bug: 19609468 Change-Id: Ic1343711c2ac0b15876b46f0b6008b0108a49470 --- api/current.txt | 19 + api/system-current.txt | 19 + core/java/android/os/Parcel.java | 20 +- .../java/android/graphics/drawable/Icon.java | 482 ++++++++++++++++++ .../tests/graphicstests/AndroidManifest.xml | 1 + .../res/drawable-nodpi/landscape.png | Bin 0 -> 24981 bytes .../android/graphics/drawable/IconTest.java | 345 +++++++++++++ 7 files changed, 885 insertions(+), 1 deletion(-) create mode 100644 graphics/java/android/graphics/drawable/Icon.java create mode 100644 graphics/tests/graphicstests/res/drawable-nodpi/landscape.png create mode 100644 graphics/tests/graphicstests/src/android/graphics/drawable/IconTest.java diff --git a/api/current.txt b/api/current.txt index be011123e549a..f8d3ef320cdbe 100644 --- a/api/current.txt +++ b/api/current.txt @@ -12486,6 +12486,25 @@ package android.graphics.drawable { enum_constant public static final android.graphics.drawable.GradientDrawable.Orientation TR_BL; } + public final class Icon implements android.os.Parcelable { + method public static android.graphics.drawable.Icon createWithBitmap(android.graphics.Bitmap); + method public static android.graphics.drawable.Icon createWithContentUri(java.lang.String); + method public static android.graphics.drawable.Icon createWithContentUri(android.net.Uri); + method public static android.graphics.drawable.Icon createWithData(byte[], int, int); + method public static android.graphics.drawable.Icon createWithFilePath(java.lang.String); + method public static android.graphics.drawable.Icon createWithResource(android.content.res.Resources, int); + method public int describeContents(); + method public android.graphics.drawable.Drawable loadDrawable(android.content.Context); + method public void loadDrawableAsync(android.content.Context, android.os.Message); + method public void loadDrawableAsync(android.content.Context, android.os.Handler, android.graphics.drawable.Icon.OnDrawableLoadedListener); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + + public static abstract interface Icon.OnDrawableLoadedListener { + method public abstract void onDrawableLoaded(android.graphics.drawable.Drawable); + } + public class InsetDrawable extends android.graphics.drawable.DrawableWrapper { ctor public InsetDrawable(android.graphics.drawable.Drawable, int); ctor public InsetDrawable(android.graphics.drawable.Drawable, int, int, int, int); diff --git a/api/system-current.txt b/api/system-current.txt index c4f96afed3553..87f53dc2437d5 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -12780,6 +12780,25 @@ package android.graphics.drawable { enum_constant public static final android.graphics.drawable.GradientDrawable.Orientation TR_BL; } + public final class Icon implements android.os.Parcelable { + method public static android.graphics.drawable.Icon createWithBitmap(android.graphics.Bitmap); + method public static android.graphics.drawable.Icon createWithContentUri(java.lang.String); + method public static android.graphics.drawable.Icon createWithContentUri(android.net.Uri); + method public static android.graphics.drawable.Icon createWithData(byte[], int, int); + method public static android.graphics.drawable.Icon createWithFilePath(java.lang.String); + method public static android.graphics.drawable.Icon createWithResource(android.content.res.Resources, int); + method public int describeContents(); + method public android.graphics.drawable.Drawable loadDrawable(android.content.Context); + method public void loadDrawableAsync(android.content.Context, android.os.Message); + method public void loadDrawableAsync(android.content.Context, android.os.Handler, android.graphics.drawable.Icon.OnDrawableLoadedListener); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + + public static abstract interface Icon.OnDrawableLoadedListener { + method public abstract void onDrawableLoaded(android.graphics.drawable.Drawable); + } + public class InsetDrawable extends android.graphics.drawable.DrawableWrapper { ctor public InsetDrawable(android.graphics.drawable.Drawable, int); ctor public InsetDrawable(android.graphics.drawable.Drawable, int, int, int, int); diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 8c1f44fa1a690..1273772bc4ec2 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -502,7 +502,25 @@ public final class Parcel { * {@SystemApi} */ public final void writeBlob(byte[] b) { - nativeWriteBlob(mNativePtr, b, 0, (b != null) ? b.length : 0); + writeBlob(b, 0, (b != null) ? b.length : 0); + } + + /** + * Write a blob of data into the parcel at the current {@link #dataPosition}, + * growing {@link #dataCapacity} if needed. + * @param b Bytes to place into the parcel. + * @param offset Index of first byte to be written. + * @param len Number of bytes to write. + * {@hide} + * {@SystemApi} + */ + public final void writeBlob(byte[] b, int offset, int len) { + if (b == null) { + writeInt(-1); + return; + } + Arrays.checkOffsetAndCount(b.length, offset, len); + nativeWriteBlob(mNativePtr, b, offset, len); } /** diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java new file mode 100644 index 0000000000000..47a1f774345ce --- /dev/null +++ b/graphics/java/android/graphics/drawable/Icon.java @@ -0,0 +1,482 @@ +/* + * 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 android.annotation.DrawableRes; +import android.content.ContentResolver; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Handler; +import android.os.Message; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.lang.IllegalArgumentException; +import java.lang.Override; + +/** + * An umbrella container for several serializable graphics representations, including Bitmaps, + * compressed bitmap images (e.g. JPG or PNG), and drawable resources (including vectors). + * + * Much ink + * 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 int TYPE_BITMAP = 1; + private static final int TYPE_RESOURCE = 2; + private static final int TYPE_DATA = 3; + private static final int TYPE_URI = 4; + + private final int mType; + + // To avoid adding unnecessary overhead, we have a few basic objects that get repurposed + // based on the value of mType. + + // TYPE_BITMAP: Bitmap + // TYPE_RESOURCE: Resources + // TYPE_DATA: DataBytes + private Object mObj1; + + // TYPE_RESOURCE: package name + // TYPE_URI: uri string + private String mString1; + + // TYPE_RESOURCE: resId + // TYPE_DATA: data length + private int mInt1; + + // TYPE_DATA: data offset + private int mInt2; + + // Internal accessors for different mType variants + private Bitmap getBitmap() { + if (mType != TYPE_BITMAP) { + throw new IllegalStateException("called getBitmap() on " + this); + } + return (Bitmap) mObj1; + } + + private int getDataLength() { + if (mType != TYPE_DATA) { + throw new IllegalStateException("called getDataLength() on " + this); + } + synchronized (this) { + return mInt1; + } + } + + private int getDataOffset() { + if (mType != TYPE_DATA) { + throw new IllegalStateException("called getDataOffset() on " + this); + } + synchronized (this) { + return mInt2; + } + } + + private byte[] getDataBytes() { + if (mType != TYPE_DATA) { + throw new IllegalStateException("called getDataBytes() on " + this); + } + synchronized (this) { + return (byte[]) mObj1; + } + } + + private Resources getResources() { + if (mType != TYPE_RESOURCE) { + throw new IllegalStateException("called getResources() on " + this); + } + return (Resources) mObj1; + } + + private String getResPackage() { + if (mType != TYPE_RESOURCE) { + throw new IllegalStateException("called getResPackage() on " + this); + } + return mString1; + } + + private int getResId() { + if (mType != TYPE_RESOURCE) { + throw new IllegalStateException("called getResId() on " + this); + } + return mInt1; + } + + private String getUriString() { + if (mType != TYPE_URI) { + throw new IllegalStateException("called getUriString() on " + this); + } + return mString1; + } + + private Uri getUri() { + return Uri.parse(getUriString()); + } + + // Convert a int32 into a four-char string + private static final String typeToString(int x) { + switch (x) { + case TYPE_BITMAP: return "BITMAP"; + case TYPE_DATA: return "DATA"; + case TYPE_RESOURCE: return "RESOURCE"; + case TYPE_URI: return "URI"; + default: return "UNKNOWN"; + } + } + + /** + * Invokes {@link #loadDrawable(Context)} on the given {@link android.os.Handler Handler} + * and then sends andThen 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(Context context, 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 then runs andThen on the UI thread when finished. + * + * @param context {@link android.content.Context Context} in which to load the drawable; see + * {@link #loadDrawable(Context)} + * @param handler {@link android.os.Handler} on which to run andThen. + * @param listener a callback to run on the provided + * Handler once the drawable is available. + */ + public void loadDrawableAsync(Context context, Handler handler, + final OnDrawableLoadedListener listener) { + 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 Drawable loadDrawable(Context context) { + switch (mType) { + case TYPE_BITMAP: + return new BitmapDrawable(context.getResources(), getBitmap()); + case TYPE_RESOURCE: + if (getResources() == null) { + if (getResPackage() == null || "android".equals(getResPackage())) { + mObj1 = Resources.getSystem(); + } else { + final PackageManager pm = context.getPackageManager(); + try { + mObj1 = pm.getResourcesForApplication(getResPackage()); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, + String.format("Unable to find package '%s'", getResPackage()), + e); + break; + } + } + } + return getResources().getDrawable(getResId(), context.getTheme()); + case TYPE_DATA: + return new BitmapDrawable(context.getResources(), + BitmapFactory.decodeByteArray(getDataBytes(), getDataOffset(), getDataLength()) + ); + case TYPE_URI: + final Uri uri = getUri(); + final String scheme = uri.getScheme(); + InputStream is = null; + if (ContentResolver.SCHEME_CONTENT.equals(scheme) + || ContentResolver.SCHEME_FILE.equals(scheme)) { + try { + is = context.getContentResolver().openInputStream(uri); + } catch (Exception e) { + Log.w(TAG, "Unable to load image from URI: " + uri, e); + } + } else { + try { + is = new FileInputStream(new File(mString1)); + } catch (FileNotFoundException e) { + Log.w(TAG, "Unable to load image from path: " + uri, e); + } + } + if (is != null) { + return new BitmapDrawable(context.getResources(), + BitmapFactory.decodeStream(is)); + } + break; + } + return null; + } + + private Icon(int mType) { + this.mType = mType; + } + + /** + * Create a Icon pointing to a drawable resource. + * @param res Resources for a package containing the resource in question + * @param resid ID of the drawable resource + */ + public static Icon createWithResource(Resources res, @DrawableRes int resid) { + final Icon rep = new Icon(TYPE_RESOURCE); + rep.mObj1 = res; + rep.mInt1 = resid; + rep.mString1 = res.getResourcePackageName(resid); + return rep; + } + + /** + * Create a Icon pointing to a bitmap in memory. + * @param bits A valid {@link android.graphics.Bitmap} object + */ + public static Icon createWithBitmap(Bitmap bits) { + final Icon rep = new Icon(TYPE_BITMAP); + rep.mObj1 = bits; + return rep; + } + + /** + * Create a 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 data at which the bitmap data starts + * @param length Length of the bitmap data + */ + public static Icon createWithData(byte[] data, int offset, int length) { + final Icon rep = new Icon(TYPE_DATA); + rep.mObj1 = data; + rep.mInt1 = length; + rep.mInt2 = offset; + return rep; + } + + /** + * Create a Icon pointing to a content specified by URI. + * + * @param uri A uri referring to local content:// or file:// image data. + */ + public static Icon createWithContentUri(String uri) { + final Icon rep = new Icon(TYPE_URI); + rep.mString1 = uri; + return rep; + } + + /** + * Create a Icon pointing to a content specified by URI. + * + * @param uri A uri referring to local content:// or file:// image data. + */ + public static Icon createWithContentUri(Uri uri) { + final Icon rep = new Icon(TYPE_URI); + rep.mString1 = uri.toString(); + return rep; + } + + /** + * Create a Icon pointing to + * + * @param path A path to a file that contains compressed bitmap data of + * a type that {@link android.graphics.BitmapFactory} can decode. + */ + public static Icon createWithFilePath(String path) { + 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: + 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("%08x", getResId())); + break; + case TYPE_DATA: + sb.append(" len=").append(getDataLength()); + if (getDataOffset() != 0) { + sb.append(" off=").append(getDataOffset()); + } + break; + case TYPE_URI: + sb.append(" uri=").append(getUriString()); + break; + } + sb.append(")"); + return sb.toString(); + } + + /** + * Parcelable interface + */ + public int describeContents() { + return (mType == TYPE_BITMAP || mType == TYPE_DATA) + ? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0; + } + + // ===== Parcelable interface ====== + + private Icon(Parcel in) { + this(in.readInt()); + switch (mType) { + case TYPE_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; + 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: + final String uri = in.readString(); + mString1 = uri; + break; + default: + throw new RuntimeException("invalid " + + this.getClass().getSimpleName() + " type in parcel: " + mType); + } + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + switch (mType) { + case TYPE_BITMAP: + final Bitmap bits = getBitmap(); + dest.writeInt(TYPE_BITMAP); + getBitmap().writeToParcel(dest, flags); + break; + case TYPE_RESOURCE: + dest.writeInt(TYPE_RESOURCE); + dest.writeString(getResPackage()); + dest.writeInt(getResId()); + break; + case TYPE_DATA: + dest.writeInt(TYPE_DATA); + dest.writeInt(getDataLength()); + dest.writeBlob(getDataBytes(), getDataOffset(), getDataLength()); + break; + case TYPE_URI: + dest.writeInt(TYPE_URI); + dest.writeString(getUriString()); + break; + } + } + + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + public Icon createFromParcel(Parcel in) { + return new Icon(in); + } + + public Icon[] newArray(int size) { + return new Icon[size]; + } + }; + + /** + * Implement this interface to receive notification when + * {@link #loadDrawableAsync(Context, Handler, OnDrawableLoadedListener) 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); + } + } +} diff --git a/graphics/tests/graphicstests/AndroidManifest.xml b/graphics/tests/graphicstests/AndroidManifest.xml index 5fb5959bc0687..e019e2806a941 100644 --- a/graphics/tests/graphicstests/AndroidManifest.xml +++ b/graphics/tests/graphicstests/AndroidManifest.xml @@ -24,6 +24,7 @@ + diff --git a/graphics/tests/graphicstests/res/drawable-nodpi/landscape.png b/graphics/tests/graphicstests/res/drawable-nodpi/landscape.png new file mode 100644 index 0000000000000000000000000000000000000000..ddb31806eca9590022710e74404f62b1a5bc6da3 GIT binary patch literal 24981 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 imgs = new ArrayList<>(); + final Icon file1 = Icon.createWithFilePath(originalfile.getAbsolutePath()); + imgs.add(file1); + final Icon bit1 = Icon.createWithBitmap(originalbits); + imgs.add(bit1); + final Icon data1 = Icon.createWithData(pngdata, 0, pngdata.length); + imgs.add(data1); + final Icon res1 = Icon.createWithResource(getContext().getResources(), R.drawable.landscape); + imgs.add(res1); + + ArrayList test = new ArrayList<>(); + final Parcel parcel = Parcel.obtain(); + int pos = 0; + parcel.writeInt(imgs.size()); + for (Icon img : imgs) { + img.writeToParcel(parcel, 0); + L("used %d bytes parceling: %s", parcel.dataPosition() - pos, img); + pos = parcel.dataPosition(); + } + + parcel.setDataPosition(0); // rewind + final int N = parcel.readInt(); + for (int i=0; i> 16) & 0xff) + + ((color >> 8) & 0xff) + + ((color) & 0xff); + return GRADIENT[sum * (GRADIENT.length-1) / (3*0xff)]; + } + static void printBits(int[] a, int w, int h) { + final StringBuilder sb = new StringBuilder(); + for (int i=0; i