diff options
Diffstat (limited to 'graphics/java')
5 files changed, 318 insertions, 157 deletions
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java index 60416a720231..419e2b7e4818 100644 --- a/graphics/java/android/graphics/ImageDecoder.java +++ b/graphics/java/android/graphics/ImageDecoder.java @@ -16,29 +16,45 @@ package android.graphics; +import static android.system.OsConstants.SEEK_SET; + import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RawRes; +import android.content.ContentResolver; +import android.content.res.AssetFileDescriptor; import android.content.res.AssetManager; import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.NinePatchDrawable; +import android.net.Uri; +import android.system.ErrnoException; +import android.system.Os; + +import libcore.io.IoUtils; +import dalvik.system.CloseGuard; import java.nio.ByteBuffer; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.lang.ArrayIndexOutOfBoundsException; +import java.lang.AutoCloseable; import java.lang.NullPointerException; import java.lang.RuntimeException; import java.lang.annotation.Retention; import static java.lang.annotation.RetentionPolicy.SOURCE; +import java.util.concurrent.atomic.AtomicBoolean; /** * Class for decoding images as {@link Bitmap}s or {@link Drawable}s. * @hide */ -public final class ImageDecoder { +public final class ImageDecoder implements AutoCloseable { /** * Source of the encoded image data. */ @@ -47,10 +63,7 @@ public final class ImageDecoder { Resources getResources() { return null; } /* @hide */ - void close() {} - - /* @hide */ - abstract ImageDecoder createImageDecoder(); + abstract ImageDecoder createImageDecoder() throws IOException; }; private static class ByteArraySource extends Source { @@ -64,7 +77,7 @@ public final class ImageDecoder { private final int mLength; @Override - public ImageDecoder createImageDecoder() { + public ImageDecoder createImageDecoder() throws IOException { return nCreate(mData, mOffset, mLength); } } @@ -76,7 +89,7 @@ public final class ImageDecoder { private final ByteBuffer mBuffer; @Override - public ImageDecoder createImageDecoder() { + public ImageDecoder createImageDecoder() throws IOException { if (!mBuffer.isDirect() && mBuffer.hasArray()) { int offset = mBuffer.arrayOffset() + mBuffer.position(); int length = mBuffer.limit() - mBuffer.position(); @@ -86,61 +99,110 @@ public final class ImageDecoder { } } - private static class ResourceSource extends Source { - ResourceSource(Resources res, int resId) - throws Resources.NotFoundException { - // Test that the resource can be found. - InputStream is = null; + private static class ContentResolverSource extends Source { + ContentResolverSource(ContentResolver resolver, Uri uri) { + mResolver = resolver; + mUri = uri; + } + + private final ContentResolver mResolver; + private final Uri mUri; + + @Override + public ImageDecoder createImageDecoder() throws IOException { + AssetFileDescriptor assetFd = null; try { - is = res.openRawResource(resId); + if (mUri.getScheme() == ContentResolver.SCHEME_CONTENT) { + assetFd = mResolver.openTypedAssetFileDescriptor(mUri, + "image/*", null); + } else { + assetFd = mResolver.openAssetFileDescriptor(mUri, "r"); + } + } catch (FileNotFoundException e) { + // Some images cannot be opened as AssetFileDescriptors (e.g. + // bmp, ico). Open them as InputStreams. + InputStream is = mResolver.openInputStream(mUri); + if (is == null) { + throw new FileNotFoundException(mUri.toString()); + } + + return createFromStream(is); + } + + final FileDescriptor fd = assetFd.getFileDescriptor(); + final long offset = assetFd.getStartOffset(); + + ImageDecoder decoder = null; + try { + try { + Os.lseek(fd, offset, SEEK_SET); + decoder = nCreate(fd); + } catch (ErrnoException e) { + decoder = createFromStream(new FileInputStream(fd)); + } } finally { - if (is != null) { - try { - is.close(); - } catch (IOException e) { - } + if (decoder == null) { + IoUtils.closeQuietly(assetFd); + } else { + decoder.mAssetFd = assetFd; } } + return decoder; + } + } + + private static ImageDecoder createFromStream(InputStream is) throws IOException { + // Arbitrary size matches BitmapFactory. + byte[] storage = new byte[16 * 1024]; + ImageDecoder decoder = null; + try { + decoder = nCreate(is, storage); + } finally { + if (decoder == null) { + IoUtils.closeQuietly(is); + } else { + decoder.mInputStream = is; + decoder.mTempStorage = storage; + } + } + + return decoder; + } + private static class ResourceSource extends Source { + ResourceSource(Resources res, int resId) { mResources = res; mResId = resId; } final Resources mResources; final int mResId; - // This is just stored here in order to keep the underlying Asset - // alive. FIXME: Can I access the Asset (and keep it alive) without - // this object? - InputStream mInputStream; @Override public Resources getResources() { return mResources; } @Override - public ImageDecoder createImageDecoder() { - // FIXME: Can I bypass creating the stream? - try { - mInputStream = mResources.openRawResource(mResId); - } catch (Resources.NotFoundException e) { - // This should never happen, since we already tested in the - // constructor. - } - if (!(mInputStream instanceof AssetManager.AssetInputStream)) { - // This should never happen. - throw new RuntimeException("Resource is not an asset?"); - } - long asset = ((AssetManager.AssetInputStream) mInputStream).getNativeAsset(); - return nCreate(asset); - } - - @Override - public void close() { + public ImageDecoder createImageDecoder() throws IOException { + // This is just used in order to access the underlying Asset and + // keep it alive. FIXME: Can we skip creating this object? + InputStream is = null; + ImageDecoder decoder = null; try { - mInputStream.close(); - } catch (IOException e) { + is = mResources.openRawResource(mResId); + if (!(is instanceof AssetManager.AssetInputStream)) { + // This should never happen. + throw new RuntimeException("Resource is not an asset?"); + } + long asset = ((AssetManager.AssetInputStream) is).getNativeAsset(); + decoder = nCreate(asset); } finally { - mInputStream = null; + if (decoder == null) { + IoUtils.closeQuietly(is); + } else { + decoder.mInputStream = is; + } } + return decoder; } } @@ -148,29 +210,52 @@ public final class ImageDecoder { * Contains information about the encoded image. */ public static class ImageInfo { + /** + * Width of the image, without scaling or cropping. + */ public final int width; + + /** + * Height of the image, without scaling or cropping. + */ public final int height; - // TODO?: Add more info? mimetype, ninepatch etc? - ImageInfo(int width, int height) { - this.width = width; - this.height = height; + /* @hide */ + ImageDecoder decoder; + + /* @hide */ + ImageInfo(ImageDecoder decoder) { + this.width = decoder.mWidth; + this.height = decoder.mHeight; + this.decoder = decoder; + } + + /** + * The mimeType of the image, if known. + */ + public String getMimeType() { + return decoder.getMimeType(); } }; /** - * Used if the provided data is incomplete. + * Supplied to onPartialImage if the provided data is incomplete. + * + * Will never be thrown by ImageDecoder. * * There may be a partial image to display. */ - public class IncompleteException extends Exception {}; + public static class IncompleteException extends IOException {}; /** * Used if the provided data is corrupt. * - * There may be a partial image to display. + * May be thrown if there is nothing to display. + * + * If supplied to onPartialImage, there may be a correct partial image to + * display. */ - public class CorruptException extends Exception {}; + public static class CorruptException extends IOException {}; /** * Optional listener supplied to {@link #decodeDrawable} or @@ -190,18 +275,21 @@ public final class ImageDecoder { /** * Optional listener supplied to the ImageDecoder. */ - public static interface OnExceptionListener { + public static interface OnPartialImageListener { /** - * Called when there is a problem in the stream or in the data. - * FIXME: Or do not allow streams? - * FIXME: Report how much of the image has been decoded? + * Called when there is only a partial image to display. + * + * If the input is incomplete or contains an error, this listener lets + * the client know that and allows them to optionally bypass the rest + * of the decode/creation process. * - * @param e Exception containing information about the error. - * @return True to create and return a {@link Drawable}/ - * {@link Bitmap} with partial data. False to return - * {@code null}. True is the default. + * @param e IOException containing information about the error that + * interrupted the decode. + * @return True (which is the default) to create and return a + * {@link Drawable}/{@link Bitmap} with partial data. False to + * abort the decode and throw the {@link java.io.IOException}. */ - public boolean onException(Exception e); + public boolean onPartialImage(IOException e); }; // Fields @@ -218,12 +306,18 @@ public final class ImageDecoder { private boolean mAsAlphaMask = false; private Rect mCropRect; - private PostProcess mPostProcess; - private OnExceptionListener mOnExceptionListener; + private PostProcess mPostProcess; + private OnPartialImageListener mOnPartialImageListener; + // Objects for interacting with the input. + private InputStream mInputStream; + private byte[] mTempStorage; + private AssetFileDescriptor mAssetFd; + private final AtomicBoolean mClosed = new AtomicBoolean(); + private final CloseGuard mCloseGuard = CloseGuard.get(); /** - * Private constructor called by JNI. {@link #recycle} must be + * Private constructor called by JNI. {@link #close} must be * called after decoding to delete native resources. */ @SuppressWarnings("unused") @@ -233,6 +327,20 @@ public final class ImageDecoder { mHeight = height; mDesiredWidth = width; mDesiredHeight = height; + mCloseGuard.open("close"); + } + + @Override + protected void finalize() throws Throwable { + try { + if (mCloseGuard != null) { + mCloseGuard.warnIfOpen(); + } + + close(); + } finally { + super.finalize(); + } } /** @@ -243,14 +351,28 @@ public final class ImageDecoder { * // FIXME: Can be an @DrawableRes? * @return a new Source object, which can be passed to * {@link #decodeDrawable} or {@link #decodeBitmap}. - * @throws Resources.NotFoundException if the asset does not exist. */ + @NonNull public static Source createSource(@NonNull Resources res, @RawRes int resId) - throws Resources.NotFoundException { + { return new ResourceSource(res, resId); } /** + * Create a new {@link Source} from a {@link android.net.Uri}. + * + * @param cr to retrieve from. + * @param uri of the image file. + * @return a new Source object, which can be passed to + * {@link #decodeDrawable} or {@link #decodeBitmap}. + */ + @NonNull + public static Source createSource(@NonNull ContentResolver cr, + @NonNull Uri uri) { + return new ContentResolverSource(cr, uri); + } + + /** * Create a new {@link Source} from a byte array. * @param data byte array of compressed image data. * @param offset offset into data for where the decoder should begin @@ -260,7 +382,6 @@ public final class ImageDecoder { * @throws ArrayIndexOutOfBoundsException if offset and length are * not within data. */ - // TODO: Overloads that don't use offset, length public static Source createSource(@NonNull byte[] data, int offset, int length) throws ArrayIndexOutOfBoundsException { if (data == null) { @@ -275,6 +396,13 @@ public final class ImageDecoder { } /** + * See {@link #createSource(byte[], int, int). + */ + public static Source createSource(@NonNull byte[] data) { + return createSource(data, 0, data.length); + } + + /** * Create a new {@link Source} from a {@link java.nio.ByteBuffer}. * * The returned {@link Source} effectively takes ownership of the @@ -307,7 +435,7 @@ public final class ImageDecoder { + "provided " + sampleSize); } if (mNativePtr == 0) { - throw new IllegalStateException("ImageDecoder is recycled!"); + throw new IllegalStateException("ImageDecoder is closed!"); } return nGetSampledSize(mNativePtr, sampleSize); @@ -433,13 +561,13 @@ public final class ImageDecoder { } /** - * Set (replace) the {@link OnExceptionListener} on this object. + * Set (replace) the {@link OnPartialImageListener} on this object. * * Will be called if there is an error in the input. Without one, a * partial {@link Bitmap} will be created. */ - public void setOnExceptionListener(OnExceptionListener l) { - mOnExceptionListener = l; + public void setOnPartialImageListener(OnPartialImageListener l) { + mOnPartialImageListener = l; } /** @@ -500,24 +628,26 @@ public final class ImageDecoder { mAsAlphaMask = true; } - /** - * Clean up resources. - * - * ImageDecoder has a private constructor, and will always be recycled - * by decodeDrawable or decodeBitmap which creates it, so there is no - * need for a finalizer. - */ - private void recycle() { - if (mNativePtr == 0) { + @Override + public void close() { + mCloseGuard.close(); + if (!mClosed.compareAndSet(false, true)) { return; } - nRecycle(mNativePtr); + nClose(mNativePtr); mNativePtr = 0; + + IoUtils.closeQuietly(mInputStream); + IoUtils.closeQuietly(mAssetFd); + + mInputStream = null; + mAssetFd = null; + mTempStorage = null; } private void checkState() { if (mNativePtr == 0) { - throw new IllegalStateException("Cannot reuse ImageDecoder.Source!"); + throw new IllegalStateException("Cannot use closed ImageDecoder!"); } checkSubset(mDesiredWidth, mDesiredHeight, mCropRect); @@ -547,48 +677,55 @@ public final class ImageDecoder { } /** - * Create a {@link Drawable}. + * Create a {@link Drawable} from a {@code Source}. + * + * @param src representing the encoded image. + * @param listener for learning the {@link ImageInfo} and changing any + * default settings on the {@code ImageDecoder}. If not {@code null}, + * this will be called on the same thread as {@code decodeDrawable} + * before that method returns. + * @return Drawable for displaying the image. + * @throws IOException if {@code src} is not found, is an unsupported + * format, or cannot be decoded for any reason. */ - public static Drawable decodeDrawable(Source src, OnHeaderDecodedListener listener) { - ImageDecoder decoder = src.createImageDecoder(); - if (decoder == null) { - return null; - } - - if (listener != null) { - ImageInfo info = new ImageInfo(decoder.mWidth, decoder.mHeight); - listener.onHeaderDecoded(info, decoder); - } + @NonNull + public static Drawable decodeDrawable(@NonNull Source src, + @Nullable OnHeaderDecodedListener listener) throws IOException { + try (ImageDecoder decoder = src.createImageDecoder()) { + if (listener != null) { + ImageInfo info = new ImageInfo(decoder); + try { + listener.onHeaderDecoded(info, decoder); + } finally { + info.decoder = null; + } + } - decoder.checkState(); + decoder.checkState(); - if (decoder.mRequireUnpremultiplied) { - // Though this could be supported (ignored) for opaque images, it - // seems better to always report this error. - throw new IllegalStateException("Cannot decode a Drawable with" + - " unpremultiplied pixels!"); - } + if (decoder.mRequireUnpremultiplied) { + // Though this could be supported (ignored) for opaque images, + // it seems better to always report this error. + throw new IllegalStateException("Cannot decode a Drawable " + + "with unpremultiplied pixels!"); + } - if (decoder.mMutable) { - throw new IllegalStateException("Cannot decode a mutable Drawable!"); - } + if (decoder.mMutable) { + throw new IllegalStateException("Cannot decode a mutable " + + "Drawable!"); + } - try { Bitmap bm = nDecodeBitmap(decoder.mNativePtr, - decoder.mOnExceptionListener, + decoder.mOnPartialImageListener, decoder.mPostProcess, - decoder.mDesiredWidth, decoder.mDesiredHeight, + decoder.mDesiredWidth, + decoder.mDesiredHeight, decoder.mCropRect, - false, // decoder.mMutable + false, // mMutable decoder.mAllocator, - false, // decoder.mRequireUnpremultiplied + false, // mRequireUnpremultiplied decoder.mPreferRamOverQuality, - decoder.mAsAlphaMask - ); - if (bm == null) { - return null; - } - + decoder.mAsAlphaMask); Resources res = src.getResources(); if (res == null) { bm.setDensity(Bitmap.DENSITY_NONE); @@ -606,60 +743,91 @@ public final class ImageDecoder { // TODO: Handle animation. return new BitmapDrawable(res, bm); - } finally { - decoder.recycle(); - src.close(); } } /** - * Create a {@link Bitmap}. + * See {@link #decodeDrawable(Source, OnHeaderDecodedListener)}. */ - public static Bitmap decodeBitmap(Source src, OnHeaderDecodedListener listener) { - ImageDecoder decoder = src.createImageDecoder(); - if (decoder == null) { - return null; - } + @NonNull + public static Drawable decodeDrawable(@NonNull Source src) + throws IOException { + return decodeDrawable(src, null); + } - if (listener != null) { - ImageInfo info = new ImageInfo(decoder.mWidth, decoder.mHeight); - listener.onHeaderDecoded(info, decoder); - } + /** + * Create a {@link Bitmap} from a {@code Source}. + * + * @param src representing the encoded image. + * @param listener for learning the {@link ImageInfo} and changing any + * default settings on the {@code ImageDecoder}. If not {@code null}, + * this will be called on the same thread as {@code decodeBitmap} + * before that method returns. + * @return Bitmap containing the image. + * @throws IOException if {@code src} is not found, is an unsupported + * format, or cannot be decoded for any reason. + */ + @NonNull + public static Bitmap decodeBitmap(@NonNull Source src, + @Nullable OnHeaderDecodedListener listener) throws IOException { + try (ImageDecoder decoder = src.createImageDecoder()) { + if (listener != null) { + ImageInfo info = new ImageInfo(decoder); + try { + listener.onHeaderDecoded(info, decoder); + } finally { + info.decoder = null; + } + } - decoder.checkState(); + decoder.checkState(); - try { return nDecodeBitmap(decoder.mNativePtr, - decoder.mOnExceptionListener, + decoder.mOnPartialImageListener, decoder.mPostProcess, - decoder.mDesiredWidth, decoder.mDesiredHeight, + decoder.mDesiredWidth, + decoder.mDesiredHeight, decoder.mCropRect, decoder.mMutable, decoder.mAllocator, decoder.mRequireUnpremultiplied, decoder.mPreferRamOverQuality, decoder.mAsAlphaMask); - } finally { - decoder.recycle(); - src.close(); } } - private static native ImageDecoder nCreate(long asset); + private String getMimeType() { + return nGetMimeType(mNativePtr); + } + + /** + * See {@link #decodeBitmap(Source, OnHeaderDecodedListener)}. + */ + @NonNull + public static Bitmap decodeBitmap(@NonNull Source src) throws IOException { + return decodeBitmap(src, null); + } + + private static native ImageDecoder nCreate(long asset) throws IOException; private static native ImageDecoder nCreate(ByteBuffer buffer, int position, - int limit); + int limit) throws IOException; private static native ImageDecoder nCreate(byte[] data, int offset, - int length); + int length) throws IOException; + private static native ImageDecoder nCreate(InputStream is, byte[] storage); + private static native ImageDecoder nCreate(FileDescriptor fd) throws IOException; + @NonNull private static native Bitmap nDecodeBitmap(long nativePtr, - OnExceptionListener listener, + OnPartialImageListener listener, PostProcess postProcess, int width, int height, Rect cropRect, boolean mutable, int allocator, boolean requireUnpremul, - boolean preferRamOverQuality, boolean asAlphaMask); + boolean preferRamOverQuality, boolean asAlphaMask) + throws IOException; private static native Point nGetSampledSize(long nativePtr, int sampleSize); private static native void nGetPadding(long nativePtr, Rect outRect); - private static native void nRecycle(long nativePtr); + private static native void nClose(long nativePtr); + private static native String nGetMimeType(long nativePtr); } diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 3d65bd226faf..68b7ac287e98 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -429,7 +429,7 @@ public class Typeface { } /** - * Sets an index of the font collection. + * Sets an index of the font collection. See {@link android.R.attr#ttcIndex}. * * Can not be used for Typeface source. build() method will return null for invalid index. * @param ttcIndex An index of the font collection. If the font source is not font diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java index c329918afc27..749b75941ef9 100644 --- a/graphics/java/android/graphics/drawable/Icon.java +++ b/graphics/java/android/graphics/drawable/Icon.java @@ -819,8 +819,10 @@ public final class Icon implements Parcelable { if (bitmapWidth > maxWidth || bitmapHeight > maxHeight) { float scale = Math.min((float) maxWidth / bitmapWidth, (float) maxHeight / bitmapHeight); - bitmap = Bitmap.createScaledBitmap(bitmap, (int) (scale * bitmapWidth), - (int) (scale * bitmapHeight), true /* filter */); + bitmap = Bitmap.createScaledBitmap(bitmap, + Math.max(1, (int) (scale * bitmapWidth)), + Math.max(1, (int) (scale * bitmapHeight)), + true /* filter */); } return bitmap; } diff --git a/graphics/java/android/graphics/drawable/RippleBackground.java b/graphics/java/android/graphics/drawable/RippleBackground.java index dea194e4ffde..4571553d4d4e 100644 --- a/graphics/java/android/graphics/drawable/RippleBackground.java +++ b/graphics/java/android/graphics/drawable/RippleBackground.java @@ -16,17 +16,12 @@ package android.graphics.drawable; -import android.animation.Animator; -import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; import android.graphics.Canvas; -import android.graphics.CanvasProperty; import android.graphics.Paint; import android.graphics.Rect; import android.util.FloatProperty; -import android.view.DisplayListCanvas; -import android.view.RenderNodeAnimator; import android.view.animation.LinearInterpolator; /** @@ -78,8 +73,8 @@ class RippleBackground extends RippleComponent { private void onStateChanged(boolean animateChanged) { float newOpacity = 0.0f; - if (mHovered) newOpacity += 1.0f; - if (mFocused) newOpacity += 1.0f; + if (mHovered) newOpacity += .25f; + if (mFocused) newOpacity += .75f; if (mAnimator != null) { mAnimator.cancel(); mAnimator = null; diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java index 734cff542c51..b883656d784a 100644 --- a/graphics/java/android/graphics/drawable/RippleDrawable.java +++ b/graphics/java/android/graphics/drawable/RippleDrawable.java @@ -264,8 +264,8 @@ public class RippleDrawable extends LayerDrawable { } setRippleActive(enabled && pressed); - setBackgroundActive(hovered, focused); + return changed; } @@ -879,22 +879,18 @@ public class RippleDrawable extends LayerDrawable { // Grab the color for the current state and cut the alpha channel in // half so that the ripple and background together yield full alpha. final int color = mState.mColor.getColorForState(getState(), Color.BLACK); - final int halfAlpha = (Color.alpha(color) / 2) << 24; final Paint p = mRipplePaint; if (mMaskColorFilter != null) { // The ripple timing depends on the paint's alpha value, so we need // to push just the alpha channel into the paint and let the filter // handle the full-alpha color. - final int fullAlphaColor = color | (0xFF << 24); - mMaskColorFilter.setColor(fullAlphaColor); - - p.setColor(halfAlpha); + mMaskColorFilter.setColor(color | 0xFF000000); + p.setColor(color & 0xFF000000); p.setColorFilter(mMaskColorFilter); p.setShader(mMaskShader); } else { - final int halfAlphaColor = (color & 0xFFFFFF) | halfAlpha; - p.setColor(halfAlphaColor); + p.setColor(color); p.setColorFilter(null); p.setShader(null); } |
