Re-write onPartialImage API

Bug: 73788969
Test: If9e27a6ce2604128a619bc4843d62711f94b4d87

Add a new Exception subclass that contains information about the type of
error, and the original Exception, if any. Remove the old
IncompleteException class. If the decode creates a partial image, pass
the information up to Java, where we create the new Exception and pass
it to the callback and/or throw it. Rewrite nDecodeBitmap to always take
the ImageDecoder as a parameter for this callback, and simply use a
boolean to determine whether to call onPostProcess

Check for exceptions in some overlooked cases in native code, and
route to the new type.

Remove FIXME to avoid parsing the whole image. In my limited testing,
it didn't seem to speed anything up, and this should be called in a
background thread anyway. Parsing now also ensures that we've read the
stream when we can have a chance to handle the exception from the right
place.

Remove fixme for b/70626068, which has been marked as WontFix.

Add a TestApi for testing an Exception thrown by an InputStream.

Remove onPartialImage from hiddenapi-light-greylist.txt to fix the build
error this change introduces. onPartialImage was erroneously added to
the list.

Change-Id: I12f69857328e63c993bd669412b06addeb6a74f1
This commit is contained in:
Leon Scroggins III
2018-03-14 16:07:43 -04:00
parent f344b3ead5
commit 1d2bf2b846
7 changed files with 216 additions and 124 deletions

View File

@@ -13655,7 +13655,9 @@ package android.graphics {
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();
}
public static class ImageDecoder.ImageInfo {
@@ -13664,16 +13666,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 {

View File

@@ -157,6 +157,10 @@ package android.graphics {
method public deprecated android.graphics.ImageDecoder setAsAlphaMask(boolean);
}
public static deprecated class ImageDecoder.IncompleteException extends java.io.IOException {
ctor public ImageDecoder.IncompleteException();
}
public deprecated class LayerRasterizer extends android.graphics.Rasterizer {
ctor public LayerRasterizer();
method public void addLayer(android.graphics.Paint, float, float);

View File

@@ -294,6 +294,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 {

View File

@@ -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

View File

@@ -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::kSourceError, "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::kSourceError, 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::kSourceError, "", 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::kSourceError,
"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::kSourceError, "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::kSourceError, "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::kSourceError, "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::kSourceError;
}
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");

View File

@@ -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,
kSourceError = 3,
};
// These need to stay in sync with PixelFormat.java's Format constants.
enum PixelFormat {
kUnknown = 0,

View File

@@ -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 {};
/**
@@ -468,10 +472,70 @@ public final class ImageDecoder implements AutoCloseable {
*/
public static final int ERROR_SOURCE_ERROR = 3;
/** @hide **/
@Retention(SOURCE)
@IntDef({ ERROR_SOURCE_EXCEPTION, ERROR_SOURCE_INCOMPLETE, ERROR_SOURCE_ERROR })
@IntDef(value = { ERROR_SOURCE_EXCEPTION, ERROR_SOURCE_INCOMPLETE, ERROR_SOURCE_ERROR },
prefix = {"ERROR_"})
public @interface Error {};
/**
* Information about an interrupted decode.
*/
public static final class DecodeException extends IOException {
@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 #ERROR_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 ERROR_SOURCE_EXCEPTION:
return "Exception in input: " + cause;
case ERROR_SOURCE_INCOMPLETE:
return "Input was incomplete.";
case ERROR_SOURCE_ERROR:
return "Input contained an error.";
default:
return "";
}
}
}
/**
* Optional listener supplied to the ImageDecoder.
*
@@ -486,13 +550,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 +730,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 +1151,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 +1368,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(@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,