diff options
| -rw-r--r-- | api/current.txt | 16 | ||||
| -rw-r--r-- | api/removed.txt | 7 | ||||
| -rw-r--r-- | api/test-current.txt | 8 | ||||
| -rw-r--r-- | config/hiddenapi-light-greylist.txt | 1 | ||||
| -rw-r--r-- | core/jni/android/graphics/ImageDecoder.cpp | 157 | ||||
| -rw-r--r-- | core/jni/android/graphics/ImageDecoder.h | 7 | ||||
| -rw-r--r-- | core/res/res/values/attrs.xml | 3 | ||||
| -rw-r--r-- | graphics/java/android/graphics/ImageDecoder.java | 187 | ||||
| -rw-r--r-- | graphics/java/android/graphics/drawable/AnimatedImageDrawable.java | 6 |
9 files changed, 257 insertions, 135 deletions
diff --git a/api/current.txt b/api/current.txt index 999a6fbf26cc..7439d1d04ad9 100644 --- a/api/current.txt +++ b/api/current.txt @@ -13645,12 +13645,14 @@ package android.graphics { field public static final int ALLOCATOR_HARDWARE = 3; // 0x3 field public static final int ALLOCATOR_SHARED_MEMORY = 2; // 0x2 field public static final int ALLOCATOR_SOFTWARE = 1; // 0x1 - field public static final int ERROR_SOURCE_ERROR = 3; // 0x3 - field public static final int ERROR_SOURCE_EXCEPTION = 1; // 0x1 - field public static final int ERROR_SOURCE_INCOMPLETE = 2; // 0x2 } - public static abstract class ImageDecoder.Error implements java.lang.annotation.Annotation { + public static final class ImageDecoder.DecodeException extends java.io.IOException { + method public int getError(); + method public android.graphics.ImageDecoder.Source getSource(); + field public static final int SOURCE_EXCEPTION = 1; // 0x1 + field public static final int SOURCE_INCOMPLETE = 2; // 0x2 + field public static final int SOURCE_MALFORMED_DATA = 3; // 0x3 } public static class ImageDecoder.ImageInfo { @@ -13659,16 +13661,12 @@ package android.graphics { method public boolean isAnimated(); } - public static class ImageDecoder.IncompleteException extends java.io.IOException { - ctor public ImageDecoder.IncompleteException(); - } - public static abstract interface ImageDecoder.OnHeaderDecodedListener { method public abstract void onHeaderDecoded(android.graphics.ImageDecoder, android.graphics.ImageDecoder.ImageInfo, android.graphics.ImageDecoder.Source); } public static abstract interface ImageDecoder.OnPartialImageListener { - method public abstract boolean onPartialImage(int, android.graphics.ImageDecoder.Source); + method public abstract boolean onPartialImage(android.graphics.ImageDecoder.DecodeException); } public static abstract class ImageDecoder.Source { diff --git a/api/removed.txt b/api/removed.txt index a5370f48fb3a..1228fd18c71a 100644 --- a/api/removed.txt +++ b/api/removed.txt @@ -155,6 +155,13 @@ package android.graphics { public final class ImageDecoder implements java.lang.AutoCloseable { method public deprecated boolean getAsAlphaMask(); method public deprecated android.graphics.ImageDecoder setAsAlphaMask(boolean); + field public static final deprecated int ERROR_SOURCE_ERROR = 3; // 0x3 + field public static final deprecated int ERROR_SOURCE_EXCEPTION = 1; // 0x1 + field public static final deprecated int ERROR_SOURCE_INCOMPLETE = 2; // 0x2 + } + + public static deprecated class ImageDecoder.IncompleteException extends java.io.IOException { + ctor public ImageDecoder.IncompleteException(); } public deprecated class LayerRasterizer extends android.graphics.Rasterizer { diff --git a/api/test-current.txt b/api/test-current.txt index 852bebfcb5e2..81cebf04f7e0 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -295,6 +295,14 @@ package android.database.sqlite { } +package android.graphics { + + public final class ImageDecoder implements java.lang.AutoCloseable { + method public static android.graphics.ImageDecoder.Source createSource(android.content.res.Resources, java.io.InputStream, int); + } + +} + package android.graphics.drawable { public class AdaptiveIconDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback { diff --git a/config/hiddenapi-light-greylist.txt b/config/hiddenapi-light-greylist.txt index e4df85bf2dee..77c2f8b286f0 100644 --- a/config/hiddenapi-light-greylist.txt +++ b/config/hiddenapi-light-greylist.txt @@ -669,7 +669,6 @@ Landroid/graphics/GraphicBuffer;->CREATOR:Landroid/os/Parcelable$Creator; Landroid/graphics/GraphicBuffer;-><init>(IIIIJ)V Landroid/graphics/GraphicBuffer;->mNativeObject:J Landroid/graphics/ImageDecoder;-><init>(JIIZ)V -Landroid/graphics/ImageDecoder;->onPartialImage(I)Z Landroid/graphics/ImageDecoder;->postProcessAndRelease(Landroid/graphics/Canvas;)I Landroid/graphics/LinearGradient;->mColors:[I Landroid/graphics/Matrix;->native_instance:J diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp index e2ce1a465fd7..726c450a4af2 100644 --- a/core/jni/android/graphics/ImageDecoder.cpp +++ b/core/jni/android/graphics/ImageDecoder.cpp @@ -38,48 +38,81 @@ using namespace android; static jclass gImageDecoder_class; static jclass gSize_class; -static jclass gIncomplete_class; +static jclass gDecodeException_class; static jclass gCanvas_class; static jmethodID gImageDecoder_constructorMethodID; static jmethodID gImageDecoder_postProcessMethodID; static jmethodID gSize_constructorMethodID; -static jmethodID gIncomplete_constructorMethodID; +static jmethodID gDecodeException_constructorMethodID; static jmethodID gCallback_onPartialImageMethodID; static jmethodID gCanvas_constructorMethodID; static jmethodID gCanvas_releaseMethodID; -static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> stream) { +// Clear and return any pending exception for handling other than throwing directly. +static jthrowable get_and_clear_exception(JNIEnv* env) { + jthrowable jexception = env->ExceptionOccurred(); + if (jexception) { + env->ExceptionClear(); + } + return jexception; +} + +// Throw a new ImageDecoder.DecodeException. Returns null for convenience. +static jobject throw_exception(JNIEnv* env, ImageDecoder::Error error, const char* msg, + jthrowable cause, jobject source) { + jstring jstr = nullptr; + if (msg) { + jstr = env->NewStringUTF(msg); + if (!jstr) { + // Out of memory. + return nullptr; + } + } + jthrowable exception = (jthrowable) env->NewObject(gDecodeException_class, + gDecodeException_constructorMethodID, error, jstr, cause, source); + // Only throw if not out of memory. + if (exception) { + env->Throw(exception); + } + return nullptr; +} + +static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> stream, jobject source) { if (!stream.get()) { - doThrowIOE(env, "Failed to create a stream"); - return nullptr; + return throw_exception(env, ImageDecoder::kSourceMalformedData, "Failed to create a stream", + nullptr, source); } std::unique_ptr<ImageDecoder> decoder(new ImageDecoder); SkCodec::Result result; auto codec = SkCodec::MakeFromStream(std::move(stream), &result, decoder->mPeeker.get()); + if (jthrowable jexception = get_and_clear_exception(env)) { + return throw_exception(env, ImageDecoder::kSourceException, "", jexception, source); + } if (!codec) { switch (result) { case SkCodec::kIncompleteInput: - env->ThrowNew(gIncomplete_class, "Incomplete input"); - break; + return throw_exception(env, ImageDecoder::kSourceIncomplete, "", nullptr, source); default: SkString msg; msg.printf("Failed to create image decoder with message '%s'", SkCodec::ResultToString(result)); - doThrowIOE(env, msg.c_str()); - break; + return throw_exception(env, ImageDecoder::kSourceMalformedData, msg.c_str(), + nullptr, source); } - return nullptr; } - // FIXME: Avoid parsing the whole image? const bool animated = codec->getFrameCount() > 1; + if (jthrowable jexception = get_and_clear_exception(env)) { + return throw_exception(env, ImageDecoder::kSourceException, "", jexception, source); + } + decoder->mCodec = SkAndroidCodec::MakeFromCodec(std::move(codec), SkAndroidCodec::ExifOrientationBehavior::kRespect); if (!decoder->mCodec.get()) { - doThrowIOE(env, "Could not create AndroidCodec"); - return nullptr; + return throw_exception(env, ImageDecoder::kSourceMalformedData, "", nullptr, source); } + const auto& info = decoder->mCodec->getInfo(); const int width = info.width(); const int height = info.height(); @@ -89,26 +122,26 @@ static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> stream) { } static jobject ImageDecoder_nCreateFd(JNIEnv* env, jobject /*clazz*/, - jobject fileDescriptor) { + jobject fileDescriptor, jobject source) { int descriptor = jniGetFDFromFileDescriptor(env, fileDescriptor); struct stat fdStat; if (fstat(descriptor, &fdStat) == -1) { - doThrowIOE(env, "broken file descriptor; fstat returned -1"); - return nullptr; + return throw_exception(env, ImageDecoder::kSourceMalformedData, + "broken file descriptor; fstat returned -1", nullptr, source); } int dupDescriptor = dup(descriptor); FILE* file = fdopen(dupDescriptor, "r"); if (file == NULL) { close(dupDescriptor); - doThrowIOE(env, "Could not open file"); - return nullptr; + return throw_exception(env, ImageDecoder::kSourceMalformedData, "Could not open file", + nullptr, source); } std::unique_ptr<SkFILEStream> fileStream(new SkFILEStream(file)); if (::lseek(descriptor, 0, SEEK_CUR) == 0) { - return native_create(env, std::move(fileStream)); + return native_create(env, std::move(fileStream), source); } // FIXME: This allows us to pretend the current location is the beginning, @@ -116,44 +149,46 @@ static jobject ImageDecoder_nCreateFd(JNIEnv* env, jobject /*clazz*/, // point as the beginning. std::unique_ptr<SkStream> stream(SkFrontBufferedStream::Make(std::move(fileStream), SkCodec::MinBufferedBytesNeeded())); - return native_create(env, std::move(stream)); + return native_create(env, std::move(stream), source); } static jobject ImageDecoder_nCreateInputStream(JNIEnv* env, jobject /*clazz*/, - jobject is, jbyteArray storage) { + jobject is, jbyteArray storage, jobject source) { std::unique_ptr<SkStream> stream(CreateJavaInputStreamAdaptor(env, is, storage, false)); if (!stream.get()) { - doThrowIOE(env, "Failed to create stream!"); - return nullptr; + return throw_exception(env, ImageDecoder::kSourceMalformedData, "Failed to create a stream", + nullptr, source); } + std::unique_ptr<SkStream> bufferedStream( SkFrontBufferedStream::Make(std::move(stream), SkCodec::MinBufferedBytesNeeded())); - return native_create(env, std::move(bufferedStream)); + return native_create(env, std::move(bufferedStream), source); } -static jobject ImageDecoder_nCreateAsset(JNIEnv* env, jobject /*clazz*/, jlong assetPtr) { +static jobject ImageDecoder_nCreateAsset(JNIEnv* env, jobject /*clazz*/, jlong assetPtr, + jobject source) { Asset* asset = reinterpret_cast<Asset*>(assetPtr); std::unique_ptr<SkStream> stream(new AssetStreamAdaptor(asset)); - return native_create(env, std::move(stream)); + return native_create(env, std::move(stream), source); } static jobject ImageDecoder_nCreateByteBuffer(JNIEnv* env, jobject /*clazz*/, jobject jbyteBuffer, - jint initialPosition, jint limit) { + jint initialPosition, jint limit, jobject source) { std::unique_ptr<SkStream> stream = CreateByteBufferStreamAdaptor(env, jbyteBuffer, initialPosition, limit); if (!stream) { - doThrowIOE(env, "Failed to read ByteBuffer"); - return nullptr; + return throw_exception(env, ImageDecoder::kSourceMalformedData, "Failed to read ByteBuffer", + nullptr, source); } - return native_create(env, std::move(stream)); + return native_create(env, std::move(stream), source); } static jobject ImageDecoder_nCreateByteArray(JNIEnv* env, jobject /*clazz*/, jbyteArray byteArray, - jint offset, jint length) { + jint offset, jint length, jobject source) { std::unique_ptr<SkStream> stream(CreateByteArrayStreamAdaptor(env, byteArray, offset, length)); - return native_create(env, std::move(stream)); + return native_create(env, std::move(stream), source); } jint postProcessAndRelease(JNIEnv* env, jobject jimageDecoder, std::unique_ptr<Canvas> canvas) { @@ -170,10 +205,8 @@ jint postProcessAndRelease(JNIEnv* env, jobject jimageDecoder, std::unique_ptr<C return env->CallIntMethod(jimageDecoder, gImageDecoder_postProcessMethodID, jcanvas); } -// Note: jpostProcess points to an ImageDecoder object if it has a PostProcess object, and nullptr -// otherwise. static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, - jobject jcallback, jobject jpostProcess, + jobject jdecoder, jboolean jpostProcess, jint desiredWidth, jint desiredHeight, jobject jsubset, jboolean requireMutable, jint allocator, jboolean requireUnpremul, jboolean preferRamOverQuality, @@ -264,11 +297,8 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong SkAndroidCodec::AndroidOptions options; options.fSampleSize = sampleSize; auto result = codec->getAndroidPixels(decodeInfo, bm.getPixels(), bm.rowBytes(), &options); - jthrowable jexception = env->ExceptionOccurred(); - if (jexception) { - env->ExceptionClear(); - } - int onPartialImageError = jexception ? 1 // ImageDecoder.java's ERROR_SOURCE_EXCEPTION + jthrowable jexception = get_and_clear_exception(env); + int onPartialImageError = jexception ? ImageDecoder::kSourceException : 0; // No error. switch (result) { case SkCodec::kSuccess: @@ -278,12 +308,12 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong break; case SkCodec::kIncompleteInput: if (!jexception) { - onPartialImageError = 2; // ImageDecoder.java's ERROR_SOURCE_EXCEPTION + onPartialImageError = ImageDecoder::kSourceIncomplete; } break; case SkCodec::kErrorInInput: if (!jexception) { - onPartialImageError = 3; // ImageDecoder.java's ERROR_SOURCE_ERROR + onPartialImageError = ImageDecoder::kSourceMalformedData; } break; default: @@ -293,24 +323,12 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong return nullptr; } - if (jexception || onPartialImageError) { - bool throwException = !jcallback || - !env->CallBooleanMethod(jcallback, gCallback_onPartialImageMethodID, - onPartialImageError); + if (onPartialImageError) { + env->CallVoidMethod(jdecoder, gCallback_onPartialImageMethodID, onPartialImageError, + jexception); if (env->ExceptionCheck()) { return nullptr; } - - if (throwException) { - if (jexception) { - env->Throw(jexception); - } else if (onPartialImageError == 2) { - env->ThrowNew(gIncomplete_class, "Incomplete input"); - } else { - doThrowIOE(env, "image has an error!"); - } - return nullptr; - } } float scaleX = 1.0f; @@ -357,11 +375,6 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong SkIRect subset; GraphicsJNI::jrect_to_irect(env, jsubset, &subset); - // FIXME: If there is no scale, should this instead call - // SkBitmap::extractSubset? If we could upload a subset - // (b/70626068), this would save memory and time. Even for a - // software Bitmap, the extra speed might be worth the memory - // tradeoff if the subset is large? translateX = -subset.fLeft; translateY = -subset.fTop; desiredWidth = subset.width(); @@ -404,7 +417,7 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong if (jpostProcess) { std::unique_ptr<Canvas> canvas(Canvas::create_canvas(bm)); - jint pixelFormat = postProcessAndRelease(env, jpostProcess, std::move(canvas)); + jint pixelFormat = postProcessAndRelease(env, jdecoder, std::move(canvas)); if (env->ExceptionCheck()) { return nullptr; } @@ -495,12 +508,12 @@ static jstring ImageDecoder_nGetMimeType(JNIEnv* env, jobject /*clazz*/, jlong n } static const JNINativeMethod gImageDecoderMethods[] = { - { "nCreate", "(J)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateAsset }, - { "nCreate", "(Ljava/nio/ByteBuffer;II)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateByteBuffer }, - { "nCreate", "([BII)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateByteArray }, - { "nCreate", "(Ljava/io/InputStream;[B)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateInputStream }, - { "nCreate", "(Ljava/io/FileDescriptor;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateFd }, - { "nDecodeBitmap", "(JLandroid/graphics/ImageDecoder;Landroid/graphics/ImageDecoder;IILandroid/graphics/Rect;ZIZZZ)Landroid/graphics/Bitmap;", + { "nCreate", "(JLandroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateAsset }, + { "nCreate", "(Ljava/nio/ByteBuffer;IILandroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateByteBuffer }, + { "nCreate", "([BIILandroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateByteArray }, + { "nCreate", "(Ljava/io/InputStream;[BLandroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateInputStream }, + { "nCreate", "(Ljava/io/FileDescriptor;Landroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateFd }, + { "nDecodeBitmap", "(JLandroid/graphics/ImageDecoder;ZIILandroid/graphics/Rect;ZIZZZ)Landroid/graphics/Bitmap;", (void*) ImageDecoder_nDecodeBitmap }, { "nGetSampledSize","(JI)Landroid/util/Size;", (void*) ImageDecoder_nGetSampledSize }, { "nGetPadding", "(JLandroid/graphics/Rect;)V", (void*) ImageDecoder_nGetPadding }, @@ -516,10 +529,10 @@ int register_android_graphics_ImageDecoder(JNIEnv* env) { gSize_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/util/Size")); gSize_constructorMethodID = GetMethodIDOrDie(env, gSize_class, "<init>", "(II)V"); - gIncomplete_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ImageDecoder$IncompleteException")); - gIncomplete_constructorMethodID = GetMethodIDOrDie(env, gIncomplete_class, "<init>", "()V"); + gDecodeException_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ImageDecoder$DecodeException")); + gDecodeException_constructorMethodID = GetMethodIDOrDie(env, gDecodeException_class, "<init>", "(ILjava/lang/String;Ljava/lang/Throwable;Landroid/graphics/ImageDecoder$Source;)V"); - gCallback_onPartialImageMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "onPartialImage", "(I)Z"); + gCallback_onPartialImageMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "onPartialImage", "(ILjava/lang/Throwable;)V"); gCanvas_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Canvas")); gCanvas_constructorMethodID = GetMethodIDOrDie(env, gCanvas_class, "<init>", "(J)V"); diff --git a/core/jni/android/graphics/ImageDecoder.h b/core/jni/android/graphics/ImageDecoder.h index 5d7e67690c22..fd9827bd74ca 100644 --- a/core/jni/android/graphics/ImageDecoder.h +++ b/core/jni/android/graphics/ImageDecoder.h @@ -33,6 +33,13 @@ struct ImageDecoder { kHardware_Allocator = 3, }; + // These need to stay in sync with ImageDecoder.java's Error constants. + enum Error { + kSourceException = 1, + kSourceIncomplete = 2, + kSourceMalformedData = 3, + }; + // These need to stay in sync with PixelFormat.java's Format constants. enum PixelFormat { kUnknown = 0, diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 75b3bcf54b62..62d5d37aa1c7 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -5968,6 +5968,9 @@ in the encoded data. Setting this to infinite (-1) will result in the animation repeating as long as it is displayed (once start() is called). --> <attr name="repeatCount"/> + <!-- When true, automatically start animating. The default is false, meaning + that the animation will not start until start() is called. --> + <attr name="autoStart" /> </declare-styleable> <!-- Drawable used to draw bitmaps. --> diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java index 2f09c65a9bf7..5ca0ad63159f 100644 --- a/graphics/java/android/graphics/ImageDecoder.java +++ b/graphics/java/android/graphics/ImageDecoder.java @@ -24,6 +24,7 @@ import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TestApi; import android.content.ContentResolver; import android.content.res.AssetFileDescriptor; import android.content.res.AssetManager; @@ -102,7 +103,7 @@ public final class ImageDecoder implements AutoCloseable { @Override public ImageDecoder createImageDecoder() throws IOException { - return nCreate(mData, mOffset, mLength); + return nCreate(mData, mOffset, mLength, this); } } @@ -117,9 +118,9 @@ public final class ImageDecoder implements AutoCloseable { if (!mBuffer.isDirect() && mBuffer.hasArray()) { int offset = mBuffer.arrayOffset() + mBuffer.position(); int length = mBuffer.limit() - mBuffer.position(); - return nCreate(mBuffer.array(), offset, length); + return nCreate(mBuffer.array(), offset, length, this); } - return nCreate(mBuffer, mBuffer.position(), mBuffer.limit()); + return nCreate(mBuffer, mBuffer.position(), mBuffer.limit(), this); } } @@ -156,7 +157,7 @@ public final class ImageDecoder implements AutoCloseable { throw new FileNotFoundException(mUri.toString()); } - return createFromStream(is, true); + return createFromStream(is, true, this); } final FileDescriptor fd = assetFd.getFileDescriptor(); @@ -166,9 +167,9 @@ public final class ImageDecoder implements AutoCloseable { try { try { Os.lseek(fd, offset, SEEK_SET); - decoder = nCreate(fd); + decoder = nCreate(fd, this); } catch (ErrnoException e) { - decoder = createFromStream(new FileInputStream(fd), true); + decoder = createFromStream(new FileInputStream(fd), true, this); } } finally { if (decoder == null) { @@ -182,18 +183,19 @@ public final class ImageDecoder implements AutoCloseable { } @NonNull - private static ImageDecoder createFromFile(@NonNull File file) throws IOException { + private static ImageDecoder createFromFile(@NonNull File file, + @NonNull Source source) throws IOException { FileInputStream stream = new FileInputStream(file); FileDescriptor fd = stream.getFD(); try { Os.lseek(fd, 0, SEEK_CUR); } catch (ErrnoException e) { - return createFromStream(stream, true); + return createFromStream(stream, true, source); } ImageDecoder decoder = null; try { - decoder = nCreate(fd); + decoder = nCreate(fd, source); } finally { if (decoder == null) { IoUtils.closeQuietly(stream); @@ -207,12 +209,12 @@ public final class ImageDecoder implements AutoCloseable { @NonNull private static ImageDecoder createFromStream(@NonNull InputStream is, - boolean closeInputStream) throws IOException { + boolean closeInputStream, Source source) throws IOException { // Arbitrary size matches BitmapFactory. byte[] storage = new byte[16 * 1024]; ImageDecoder decoder = null; try { - decoder = nCreate(is, storage); + decoder = nCreate(is, storage, source); } finally { if (decoder == null) { if (closeInputStream) { @@ -260,7 +262,7 @@ public final class ImageDecoder implements AutoCloseable { } InputStream is = mInputStream; mInputStream = null; - return createFromStream(is, false); + return createFromStream(is, false, this); } } } @@ -305,7 +307,7 @@ public final class ImageDecoder implements AutoCloseable { } AssetInputStream ais = mAssetInputStream; mAssetInputStream = null; - return createFromAsset(ais); + return createFromAsset(ais, this); } } } @@ -340,18 +342,19 @@ public final class ImageDecoder implements AutoCloseable { mResDensity = value.density; } - return createFromAsset((AssetInputStream) is); + return createFromAsset((AssetInputStream) is, this); } } /** * ImageDecoder will own the AssetInputStream. */ - private static ImageDecoder createFromAsset(AssetInputStream ais) throws IOException { + private static ImageDecoder createFromAsset(AssetInputStream ais, + Source source) throws IOException { ImageDecoder decoder = null; try { long asset = ais.getNativeAsset(); - decoder = nCreate(asset); + decoder = nCreate(asset, source); } finally { if (decoder == null) { IoUtils.closeQuietly(ais); @@ -375,7 +378,7 @@ public final class ImageDecoder implements AutoCloseable { @Override public ImageDecoder createImageDecoder() throws IOException { InputStream is = mAssets.open(mFileName); - return createFromAsset((AssetInputStream) is); + return createFromAsset((AssetInputStream) is, this); } } @@ -388,7 +391,7 @@ public final class ImageDecoder implements AutoCloseable { @Override public ImageDecoder createImageDecoder() throws IOException { - return createFromFile(mFile); + return createFromFile(mFile, this); } } @@ -431,9 +434,10 @@ public final class ImageDecoder implements AutoCloseable { } }; - /** - * Thrown if the provided data is incomplete. + /** @removed + * @deprecated Subsumed by {@link #DecodeException}. */ + @java.lang.Deprecated public static class IncompleteException extends IOException {}; /** @@ -453,24 +457,102 @@ public final class ImageDecoder implements AutoCloseable { }; - /** - * An Exception was thrown reading the {@link Source}. + /** @removed + * @deprecated Replaced by {@link #DecodeException#SOURCE_EXCEPTION}. */ + @java.lang.Deprecated public static final int ERROR_SOURCE_EXCEPTION = 1; - /** - * The encoded data was incomplete. + /** @removed + * @deprecated Replaced by {@link #DecodeException#SOURCE_INCOMPLETE}. */ + @java.lang.Deprecated public static final int ERROR_SOURCE_INCOMPLETE = 2; - /** - * The encoded data contained an error. + /** @removed + * @deprecated Replaced by {@link #DecodeException#SOURCE_MALFORMED_DATA}. */ + @java.lang.Deprecated public static final int ERROR_SOURCE_ERROR = 3; - @Retention(SOURCE) - @IntDef({ ERROR_SOURCE_EXCEPTION, ERROR_SOURCE_INCOMPLETE, ERROR_SOURCE_ERROR }) - public @interface Error {}; + /** + * Information about an interrupted decode. + */ + public static final class DecodeException extends IOException { + /** + * An Exception was thrown reading the {@link Source}. + */ + public static final int SOURCE_EXCEPTION = 1; + + /** + * The encoded data was incomplete. + */ + public static final int SOURCE_INCOMPLETE = 2; + + /** + * The encoded data contained an error. + */ + public static final int SOURCE_MALFORMED_DATA = 3; + + /** @hide **/ + @Retention(SOURCE) + @IntDef(value = { SOURCE_EXCEPTION, SOURCE_INCOMPLETE, SOURCE_MALFORMED_DATA }, + prefix = {"SOURCE_"}) + public @interface Error {}; + + @Error final int mError; + @NonNull final Source mSource; + + DecodeException(@Error int error, @Nullable Throwable cause, @NonNull Source source) { + super(errorMessage(error, cause), cause); + mError = error; + mSource = source; + } + + /** + * Private method called by JNI. + */ + @SuppressWarnings("unused") + DecodeException(@Error int error, @Nullable String msg, @Nullable Throwable cause, + @NonNull Source source) { + super(msg + errorMessage(error, cause), cause); + mError = error; + mSource = source; + } + + /** + * Retrieve the reason that decoding was interrupted. + * + * <p>If the error is {@link #SOURCE_EXCEPTION}, the underlying + * {@link java.lang.Throwable} can be retrieved with + * {@link java.lang.Throwable#getCause}.</p> + */ + @Error + public int getError() { + return mError; + } + + /** + * Retrieve the {@link Source} that was interrupted. + */ + @NonNull + public Source getSource() { + return mSource; + } + + private static String errorMessage(@Error int error, @Nullable Throwable cause) { + switch (error) { + case SOURCE_EXCEPTION: + return "Exception in input: " + cause; + case SOURCE_INCOMPLETE: + return "Input was incomplete."; + case SOURCE_MALFORMED_DATA: + return "Input contained an error."; + default: + return ""; + } + } + } /** * Optional listener supplied to the ImageDecoder. @@ -486,13 +568,12 @@ public final class ImageDecoder implements AutoCloseable { * optionally finish the rest of the decode/creation process to create * a partial {@link Drawable}/{@link Bitmap}. * - * @param error indicating what interrupted the decode. - * @param source that had the error. + * @param e containing information about the decode interruption. * @return True to create and return a {@link Drawable}/{@link Bitmap} * with partial data. False (which is the default) to abort the - * decode and throw {@link java.io.IOException}. + * decode and throw {@code e}. */ - public boolean onPartialImage(@Error int error, @NonNull Source source); + boolean onPartialImage(@NonNull DecodeException e); }; // Fields @@ -667,6 +748,7 @@ public final class ImageDecoder implements AutoCloseable { * Internal API used to generate bitmaps for use by Drawables (i.e. BitmapDrawable) * @hide */ + @TestApi public static Source createSource(Resources res, InputStream is, int density) { return new InputStreamSource(res, is, density); } @@ -1087,14 +1169,8 @@ public final class ImageDecoder implements AutoCloseable { @NonNull private Bitmap decodeBitmapInternal() throws IOException { checkState(); - // nDecodeBitmap calls onPartialImage only if mOnPartialImageListener - // exists - ImageDecoder partialImagePtr = mOnPartialImageListener == null ? null : this; - // nDecodeBitmap calls postProcessAndRelease only if mPostProcessor - // exists. - ImageDecoder postProcessPtr = mPostProcessor == null ? null : this; - return nDecodeBitmap(mNativePtr, partialImagePtr, - postProcessPtr, mDesiredWidth, mDesiredHeight, mCropRect, + return nDecodeBitmap(mNativePtr, this, mPostProcessor != null, + mDesiredWidth, mDesiredHeight, mCropRect, mMutable, mAllocator, mRequireUnpremultiplied, mConserveMemory, mDecodeAsAlphaMask); } @@ -1310,23 +1386,28 @@ public final class ImageDecoder implements AutoCloseable { * Private method called by JNI. */ @SuppressWarnings("unused") - private boolean onPartialImage(@Error int error) { - return mOnPartialImageListener.onPartialImage(error, mSource); + private void onPartialImage(@DecodeException.Error int error, @Nullable Throwable cause) + throws DecodeException { + DecodeException exception = new DecodeException(error, cause, mSource); + if (mOnPartialImageListener == null + || !mOnPartialImageListener.onPartialImage(exception)) { + throw exception; + } } - private static native ImageDecoder nCreate(long asset) throws IOException; - private static native ImageDecoder nCreate(ByteBuffer buffer, - int position, - int limit) throws IOException; - private static native ImageDecoder nCreate(byte[] data, int offset, - int length) throws IOException; - private static native ImageDecoder nCreate(InputStream is, byte[] storage); + private static native ImageDecoder nCreate(long asset, Source src) throws IOException; + private static native ImageDecoder nCreate(ByteBuffer buffer, int position, + int limit, Source src) throws IOException; + private static native ImageDecoder nCreate(byte[] data, int offset, int length, + Source src) throws IOException; + private static native ImageDecoder nCreate(InputStream is, byte[] storage, + Source src) throws IOException; // The fd must be seekable. - private static native ImageDecoder nCreate(FileDescriptor fd) throws IOException; + private static native ImageDecoder nCreate(FileDescriptor fd, Source src) throws IOException; @NonNull private static native Bitmap nDecodeBitmap(long nativePtr, - @Nullable ImageDecoder partialImageListener, - @Nullable ImageDecoder postProcessor, + @NonNull ImageDecoder decoder, + boolean doPostProcess, int width, int height, @Nullable Rect cropRect, boolean mutable, int allocator, boolean requireUnpremul, diff --git a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java index a47ecf517c70..457e4aa426b8 100644 --- a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java @@ -261,6 +261,12 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { if (repeatCount != REPEAT_UNDEFINED) { this.setRepeatCount(repeatCount); } + + boolean autoStart = a.getBoolean( + R.styleable.AnimatedImageDrawable_autoStart, false); + if (autoStart && mState.mNativePtr != 0) { + this.start(); + } } /** |