Re-write onPartialImage API

am: 1d2bf2b846

Change-Id: I7436bbe89b9efd9a406958b14f79294e982c5b3a
This commit is contained in:
Leon Scroggins III
2018-03-22 23:34:41 +00:00
committed by android-build-merger
7 changed files with 216 additions and 124 deletions

View File

@@ -13638,7 +13638,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 {
@@ -13647,16 +13649,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

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

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,