Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ed074fd7 authored by Leon Scroggins III's avatar Leon Scroggins III
Browse files

Support using ImageDecoder with ContentResolver + URI

Bug: 63909536
Test: CTS: I0f36ce34c968fd7fae4d8edebabea3a421859615

Add ImageDecoder.createSource(ContentResolver, URI), allowing a client
to decode images from files, content, resources, etc.

Prefer using a file descriptor to using an InputStream so the input can
be cheaply seeked and rewound if necessary.

Make ImageDecoder implement AutoCloseable to handle closing the input.

Make decodeDrawable/decodeBitmap always return an object or throw an
IOException. Avoid checking for a file in the Source constructor.

Fix a bug where inner Exception classes were not static.

Update JavaInputStreamAdaptor to be usable by ImageDecoder:
- previously it always swallowed exceptions. Allow them to propagate
  (optionally) so that they can be reported back to the client.
- Add refs to the InputStream and byte[]. ImageDecoder returns from
  native and then uses the JavaInputStreamAdaptor again, making the
  local refs go out of scope.
- Hold on to the JavaVM and convert to the JNIEnv when necessary. Pass
  local env pointers to avoid looking it up multiple times in one call.
- If an exception is thrown inside the doRead() loop, return the number
  of bytes successfully read.

Change-Id: I869dad55521cf942efd010c06baf3f44c1c08374
parent 5c72a77b
Loading
Loading
Loading
Loading
+85 −43
Original line number Diff line number Diff line
@@ -11,21 +11,62 @@
static jmethodID    gInputStream_readMethodID;
static jmethodID    gInputStream_skipMethodID;

// FIXME: Share with ByteBufferStreamAdaptor.cpp?
static JNIEnv* get_env_or_die(JavaVM* jvm) {
    JNIEnv* env;
    if (jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
        LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", jvm);
    }
    return env;
}

/**
 *  Wrapper for a Java InputStream.
 */
class JavaInputStreamAdaptor : public SkStream {
    JavaInputStreamAdaptor(JavaVM* jvm, jobject js, jbyteArray ar, jint capacity,
                           bool swallowExceptions)
            : fJvm(jvm)
            , fJavaInputStream(js)
            , fJavaByteArray(ar)
            , fCapacity(capacity)
            , fBytesRead(0)
            , fIsAtEnd(false)
            , fSwallowExceptions(swallowExceptions) {}

public:
    JavaInputStreamAdaptor(JNIEnv* env, jobject js, jbyteArray ar)
        : fEnv(env), fJavaInputStream(js), fJavaByteArray(ar) {
        SkASSERT(ar);
        fCapacity = env->GetArrayLength(ar);
        SkASSERT(fCapacity > 0);
        fBytesRead = 0;
        fIsAtEnd = false;
    static JavaInputStreamAdaptor* Create(JNIEnv* env, jobject js, jbyteArray ar,
                                          bool swallowExceptions) {
        JavaVM* jvm;
        LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&jvm) != JNI_OK);

        js = env->NewGlobalRef(js);
        if (!js) {
            return nullptr;
        }

        ar = (jbyteArray) env->NewGlobalRef(ar);
        if (!ar) {
            env->DeleteGlobalRef(js);
            return nullptr;
        }

    virtual size_t read(void* buffer, size_t size) {
        jint capacity = env->GetArrayLength(ar);
        return new JavaInputStreamAdaptor(jvm, js, ar, capacity, swallowExceptions);
    }

    ~JavaInputStreamAdaptor() override {
        auto* env = get_env_or_die(fJvm);
        env->DeleteGlobalRef(fJavaInputStream);
        env->DeleteGlobalRef(fJavaByteArray);
    }

    size_t read(void* buffer, size_t size) override {
        auto* env = get_env_or_die(fJvm);
        if (!fSwallowExceptions && checkException(env)) {
            // Just in case the caller did not clear from a previous exception.
            return 0;
        }
        if (NULL == buffer) {
            if (0 == size) {
                return 0;
@@ -36,10 +77,10 @@ public:
                 */
                size_t amountSkipped = 0;
                do {
                    size_t amount = this->doSkip(size - amountSkipped);
                    size_t amount = this->doSkip(size - amountSkipped, env);
                    if (0 == amount) {
                        char tmp;
                        amount = this->doRead(&tmp, 1);
                        amount = this->doRead(&tmp, 1, env);
                        if (0 == amount) {
                            // if read returned 0, we're at EOF
                            fIsAtEnd = true;
@@ -51,16 +92,13 @@ public:
                return amountSkipped;
            }
        }
        return this->doRead(buffer, size);
        return this->doRead(buffer, size, env);
    }

    virtual bool isAtEnd() const {
        return fIsAtEnd;
    }
    bool isAtEnd() const override { return fIsAtEnd; }

private:
    size_t doRead(void* buffer, size_t size) {
        JNIEnv* env = fEnv;
    size_t doRead(void* buffer, size_t size, JNIEnv* env) {
        size_t bytesRead = 0;
        // read the bytes
        do {
@@ -75,13 +113,9 @@ private:

            jint n = env->CallIntMethod(fJavaInputStream,
                                        gInputStream_readMethodID, fJavaByteArray, 0, requested);
            if (env->ExceptionCheck()) {
                env->ExceptionDescribe();
                env->ExceptionClear();
            if (checkException(env)) {
                SkDebugf("---- read threw an exception\n");
                // Consider the stream to be at the end, since there was an error.
                fIsAtEnd = true;
                return 0;
                return bytesRead;
            }

            if (n < 0) { // n == 0 should not be possible, see InputStream read() specifications.
@@ -91,14 +125,9 @@ private:

            env->GetByteArrayRegion(fJavaByteArray, 0, n,
                                    reinterpret_cast<jbyte*>(buffer));
            if (env->ExceptionCheck()) {
                env->ExceptionDescribe();
                env->ExceptionClear();
            if (checkException(env)) {
                SkDebugf("---- read:GetByteArrayRegion threw an exception\n");
                // The error was not with the stream itself, but consider it to be at the
                // end, since we do not have a way to recover.
                fIsAtEnd = true;
                return 0;
                return bytesRead;
            }

            buffer = (void*)((char*)buffer + n);
@@ -110,14 +139,10 @@ private:
        return bytesRead;
    }

    size_t doSkip(size_t size) {
        JNIEnv* env = fEnv;

    size_t doSkip(size_t size, JNIEnv* env) {
        jlong skipped = env->CallLongMethod(fJavaInputStream,
                                            gInputStream_skipMethodID, (jlong)size);
        if (env->ExceptionCheck()) {
            env->ExceptionDescribe();
            env->ExceptionClear();
        if (checkException(env)) {
            SkDebugf("------- skip threw an exception\n");
            return 0;
        }
@@ -128,20 +153,37 @@ private:
        return (size_t)skipped;
    }

    JNIEnv*     fEnv;
    jobject     fJavaInputStream;   // the caller owns this object
    jbyteArray  fJavaByteArray;     // the caller owns this object
    jint        fCapacity;
    bool checkException(JNIEnv* env) {
        if (!env->ExceptionCheck()) {
            return false;
        }

        env->ExceptionDescribe();
        if (fSwallowExceptions) {
            env->ExceptionClear();
        }

        // There is no way to recover from the error, so consider the stream
        // to be at the end.
        fIsAtEnd = true;

        return true;
    }

    JavaVM*     fJvm;
    jobject     fJavaInputStream;
    jbyteArray  fJavaByteArray;
    const jint  fCapacity;
    size_t      fBytesRead;
    bool        fIsAtEnd;
    const bool  fSwallowExceptions;
};

SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream,
                                       jbyteArray storage) {
    return new JavaInputStreamAdaptor(env, stream, storage);
SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream, jbyteArray storage,
                                       bool swallowExceptions) {
    return JavaInputStreamAdaptor::Create(env, stream, storage, swallowExceptions);
}


static SkMemoryStream* adaptor_to_mem_stream(SkStream* stream) {
    SkASSERT(stream != NULL);
    size_t bufferSize = 4096;
+7 −6
Original line number Diff line number Diff line
@@ -16,13 +16,16 @@ class SkWStream;
 *  @param stream Pointer to Java InputStream.
 *  @param storage Java byte array for retrieving data from the
 *      Java InputStream.
 *  @param swallowExceptions Whether to call ExceptionClear() after
 *      an Exception is thrown. If false, it is up to the client to
 *      clear or propagate the exception.
 *  @return SkStream Simple subclass of SkStream which supports its
 *      basic methods like reading. Only valid until the calling
 *      function returns, since the Java InputStream is not managed
 *      by the SkStream.
 */
SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream,
                                       jbyteArray storage);
SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream, jbyteArray storage,
                                       bool swallowExceptions = true);

/**
 *  Copy a Java InputStream. The result will be rewindable.
@@ -33,10 +36,8 @@ SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream,
 *  @return SkStreamRewindable The data in stream will be copied
 *      to a new SkStreamRewindable.
 */
SkStreamRewindable* CopyJavaInputStream(JNIEnv* env, jobject stream,
                                        jbyteArray storage);
SkStreamRewindable* CopyJavaInputStream(JNIEnv* env, jobject stream, jbyteArray storage);

SkWStream* CreateJavaOutputStreamAdaptor(JNIEnv* env, jobject stream,
                                         jbyteArray storage);
SkWStream* CreateJavaOutputStreamAdaptor(JNIEnv* env, jobject stream, jbyteArray storage);

#endif  // _ANDROID_GRAPHICS_CREATE_JAVA_OUTPUT_STREAM_ADAPTOR_H_
+102 −34
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

#include "Bitmap.h"
#include "ByteBufferStreamAdaptor.h"
#include "CreateJavaOutputStreamAdaptor.h"
#include "GraphicsJNI.h"
#include "NinePatchPeeker.h"
#include "Utils.h"
@@ -26,10 +27,12 @@

#include <SkAndroidCodec.h>
#include <SkEncodedImageFormat.h>
#include <SkFrontBufferedStream.h>
#include <SkStream.h>

#include <androidfw/Asset.h>
#include <jni.h>
#include <sys/stat.h>

using namespace android;

@@ -69,15 +72,15 @@ struct ImageDecoder {

static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> stream) {
    if (!stream.get()) {
        return nullObjectReturn("Failed to create a stream");
        doThrowIOE(env, "Failed to create a stream");
        return nullptr;
    }
    std::unique_ptr<ImageDecoder> decoder(new ImageDecoder);
    decoder->mCodec = SkAndroidCodec::MakeFromStream(std::move(stream), &decoder->mPeeker);
    if (!decoder->mCodec.get()) {
        // FIXME: Add an error code to SkAndroidCodec::MakeFromStream, like
        // SkCodec? Then this can print a more informative error message.
        // (Or we can print one from within SkCodec.)
        ALOGE("Failed to create an SkCodec");
        // FIXME: (b/71578461) Use the error message from
        // SkCodec::MakeFromStream to report a more informative error message.
        doThrowIOE(env, "Failed to create an SkCodec");
        return nullptr;
    }

@@ -88,7 +91,52 @@ static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> stream) {
                          reinterpret_cast<jlong>(decoder.release()), width, height);
}

static jobject ImageDecoder_nCreate(JNIEnv* env, jobject /*clazz*/, jlong assetPtr) {
static jobject ImageDecoder_nCreateFd(JNIEnv* env, jobject /*clazz*/,
        jobject fileDescriptor) {
    int descriptor = jniGetFDFromFileDescriptor(env, fileDescriptor);

    struct stat fdStat;
    if (fstat(descriptor, &fdStat) == -1) {
        doThrowIOE(env, "broken file descriptor; fstat returned -1");
        return nullptr;
    }

    int dupDescriptor = dup(descriptor);
    FILE* file = fdopen(dupDescriptor, "r");
    if (file == NULL) {
        close(dupDescriptor);
        doThrowIOE(env, "Could not open file");
        return nullptr;
    }
    std::unique_ptr<SkFILEStream> fileStream(new SkFILEStream(file));

    if (::lseek(descriptor, 0, SEEK_CUR) == 0) {
        return native_create(env, std::move(fileStream));
    }

    // FIXME: This allows us to pretend the current location is the beginning,
    // but it would be better if SkFILEStream allowed treating its starting
    // point as the beginning.
    std::unique_ptr<SkStream> stream(SkFrontBufferedStream::Make(std::move(fileStream),
                SkCodec::MinBufferedBytesNeeded()));
    return native_create(env, std::move(stream));
}

static jobject ImageDecoder_nCreateInputStream(JNIEnv* env, jobject /*clazz*/,
        jobject is, jbyteArray storage) {
    std::unique_ptr<SkStream> stream(CreateJavaInputStreamAdaptor(env, is, storage, false));

    if (!stream.get()) {
        doThrowIOE(env, "Failed to create stream!");
        return nullptr;
    }
    std::unique_ptr<SkStream> bufferedStream(
        SkFrontBufferedStream::Make(std::move(stream),
        SkCodec::MinBufferedBytesNeeded()));
    return native_create(env, std::move(bufferedStream));
}

static jobject ImageDecoder_nCreateAsset(JNIEnv* env, jobject /*clazz*/, jlong assetPtr) {
    Asset* asset = reinterpret_cast<Asset*>(assetPtr);
    std::unique_ptr<SkStream> stream(new AssetStreamAdaptor(asset));
    return native_create(env, std::move(stream));
@@ -99,6 +147,7 @@ static jobject ImageDecoder_nCreateByteBuffer(JNIEnv* env, jobject /*clazz*/, jo
    std::unique_ptr<SkStream> stream = CreateByteBufferStreamAdaptor(env, jbyteBuffer,
                                                                     initialPosition, limit);
    if (!stream) {
        doThrowIOE(env, "Failed to read ByteBuffer");
        return nullptr;
    }
    return native_create(env, std::move(stream));
@@ -114,6 +163,7 @@ static bool supports_any_down_scale(const SkAndroidCodec* codec) {
    return codec->getEncodedFormat() == SkEncodedImageFormat::kWEBP;
}

// This method should never return null. Instead, it should throw an exception.
static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
                                          jobject jcallback, jobject jpostProcess,
                                          jint desiredWidth, jint desiredHeight, jobject jsubset,
@@ -165,7 +215,8 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong
        case kOpaque_SkAlphaType:
            break;
        case kUnknown_SkAlphaType:
            return nullObjectReturn("Unknown alpha type");
            doThrowIOE(env, "Unknown alpha type");
            return nullptr;
    }

    SkColorType colorType = kN32_SkColorType;
@@ -200,7 +251,8 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong
        bitmapInfo = bitmapInfo.makeColorType(kAlpha_8_SkColorType);
    }
    if (!bm.setInfo(bitmapInfo)) {
        return nullObjectReturn("Failed to setInfo properly");
        doThrowIOE(env, "Failed to setInfo properly");
        return nullptr;
    }

    sk_sp<Bitmap> nativeBitmap;
@@ -213,35 +265,44 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong
        nativeBitmap = Bitmap::allocateHeapBitmap(&bm);
    }
    if (!nativeBitmap) {
        ALOGE("OOM allocating Bitmap with dimensions %i x %i",
        SkString msg;
        msg.printf("OOM allocating Bitmap with dimensions %i x %i",
                decodeInfo.width(), decodeInfo.height());
        doThrowOOME(env);
        doThrowOOME(env, msg.c_str());
        return nullptr;
    }

    jobject jexception = nullptr;
    SkAndroidCodec::AndroidOptions options;
    options.fSampleSize = sampleSize;
    auto result = codec->getAndroidPixels(decodeInfo, bm.getPixels(), bm.rowBytes(), &options);
    jobject jexception = env->ExceptionOccurred();
    if (jexception) {
        env->ExceptionClear();
    }
    switch (result) {
        case SkCodec::kSuccess:
            // Ignore the exception, since the decode was successful anyway.
            jexception = nullptr;
            break;
        case SkCodec::kIncompleteInput:
            if (jcallback) {
            if (jcallback && !jexception) {
                jexception = env->NewObject(gIncomplete_class, gIncomplete_constructorMethodID);
            }
            break;
        case SkCodec::kErrorInInput:
            if (jcallback) {
            if (jcallback && !jexception) {
                jexception = env->NewObject(gCorrupt_class, gCorrupt_constructorMethodID);
            }
            break;
        default:
            ALOGE("getPixels failed with error %i", result);
            SkString msg;
            msg.printf("getPixels failed with error %i", result);
            doThrowIOE(env, msg.c_str());
            return nullptr;
    }

    if (jexception) {
        // FIXME: Do not provide a way for the client to force the method to return null.
        if (!env->CallBooleanMethod(jcallback, gCallback_onExceptionMethodID, jexception) ||
            env->ExceptionCheck()) {
            return nullptr;
@@ -268,7 +329,8 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong
            size_t ninePatchArraySize = decoder->mPeeker.mPatch->serializedSize();
            ninePatchChunk = env->NewByteArray(ninePatchArraySize);
            if (ninePatchChunk == nullptr) {
                return nullObjectReturn("ninePatchChunk == null");
                doThrowOOME(env, "Failed to allocate nine patch chunk.");
                return nullptr;
            }

            env->SetByteArrayRegion(ninePatchChunk, 0, decoder->mPeeker.mPatchSize,
@@ -278,7 +340,8 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong
        if (decoder->mPeeker.mHasInsets) {
            ninePatchInsets = decoder->mPeeker.createNinePatchInsets(env, 1.0f);
            if (ninePatchInsets == nullptr) {
                return nullObjectReturn("nine patch insets == null");
                doThrowOOME(env, "Failed to allocate nine patch insets.");
                return nullptr;
            }
        }
    }
@@ -303,7 +366,8 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong
        SkImageInfo scaledInfo = bitmapInfo.makeWH(desiredWidth, desiredHeight);
        SkBitmap scaledBm;
        if (!scaledBm.setInfo(scaledInfo)) {
            nullObjectReturn("Failed scaled setInfo");
            doThrowIOE(env, "Failed scaled setInfo");
            return nullptr;
        }

        sk_sp<Bitmap> scaledPixelRef;
@@ -313,9 +377,10 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong
            scaledPixelRef = Bitmap::allocateHeapBitmap(&scaledBm);
        }
        if (!scaledPixelRef) {
            ALOGE("OOM allocating scaled Bitmap with dimensions %i x %i",
            SkString msg;
            msg.printf("OOM allocating scaled Bitmap with dimensions %i x %i",
                    desiredWidth, desiredHeight);
            doThrowOOME(env);
            doThrowOOME(env, msg.c_str());
            return nullptr;
        }

@@ -334,13 +399,11 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong

    if (jpostProcess) {
        std::unique_ptr<Canvas> canvas(Canvas::create_canvas(bm));
        if (!canvas) {
            return nullObjectReturn("Failed to create Canvas for PostProcess!");
        }
        jobject jcanvas = env->NewObject(gCanvas_class, gCanvas_constructorMethodID,
                                         reinterpret_cast<jlong>(canvas.get()));
        if (!jcanvas) {
            return nullObjectReturn("Failed to create Java Canvas for PostProcess!");
            doThrowOOME(env, "Failed to create Java Canvas for PostProcess!");
            return nullptr;
        }
        // jcanvas will now own canvas.
        canvas.release();
@@ -368,15 +431,17 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong
                newAlphaType = kOpaque_SkAlphaType;
                break;
            default:
                ALOGE("invalid return from postProcess: %i", pixelFormat);
                doThrowIAE(env);
                SkString msg;
                msg.printf("invalid return from postProcess: %i", pixelFormat);
                doThrowIAE(env, msg.c_str());
                return nullptr;
        }

        if (newAlphaType != bm.alphaType()) {
            if (!bm.setAlphaType(newAlphaType)) {
                ALOGE("incompatible return from postProcess: %i", pixelFormat);
                doThrowIAE(env);
                SkString msg;
                msg.printf("incompatible return from postProcess: %i", pixelFormat);
                doThrowIAE(env, msg.c_str());
                return nullptr;
            }
            nativeBitmap->setAlphaType(newAlphaType);
@@ -405,7 +470,8 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong
                                            ninePatchChunk, ninePatchInsets);
            }
            if (allocator == ImageDecoder::kHardware_Allocator) {
                return nullObjectReturn("failed to allocate hardware Bitmap!");
                doThrowOOME(env, "failed to allocate hardware Bitmap!");
                return nullptr;
            }
            // If we failed to create a hardware bitmap, go ahead and create a
            // software one.
@@ -430,19 +496,21 @@ static void ImageDecoder_nGetPadding(JNIEnv* env, jobject /*clazz*/, jlong nativ
    decoder->mPeeker.getPadding(env, outPadding);
}

static void ImageDecoder_nRecycle(JNIEnv* /*env*/, jobject /*clazz*/, jlong nativePtr) {
static void ImageDecoder_nClose(JNIEnv* /*env*/, jobject /*clazz*/, jlong nativePtr) {
    delete reinterpret_cast<ImageDecoder*>(nativePtr);
}

static const JNINativeMethod gImageDecoderMethods[] = {
    { "nCreate",        "(J)Landroid/graphics/ImageDecoder;",    (void*) ImageDecoder_nCreate },
    { "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$OnExceptionListener;Landroid/graphics/PostProcess;IILandroid/graphics/Rect;ZIZZZ)Landroid/graphics/Bitmap;",
                                                                 (void*) ImageDecoder_nDecodeBitmap },
    { "nGetSampledSize","(JI)Landroid/graphics/Point;",          (void*) ImageDecoder_nGetSampledSize },
    { "nGetPadding",    "(JLandroid/graphics/Rect;)V",           (void*) ImageDecoder_nGetPadding },
    { "nRecycle",       "(J)V",                                  (void*) ImageDecoder_nRecycle},
    { "nClose",         "(J)V",                                  (void*) ImageDecoder_nClose},
};

int register_android_graphics_ImageDecoder(JNIEnv* env) {
@@ -459,7 +527,7 @@ int register_android_graphics_ImageDecoder(JNIEnv* env) {
    gCorrupt_constructorMethodID = GetMethodIDOrDie(env, gCorrupt_class, "<init>", "()V");

    jclass callback_class = FindClassOrDie(env, "android/graphics/ImageDecoder$OnExceptionListener");
    gCallback_onExceptionMethodID = GetMethodIDOrDie(env, callback_class, "onException", "(Ljava/lang/Exception;)Z");
    gCallback_onExceptionMethodID = GetMethodIDOrDie(env, callback_class, "onException", "(Ljava/io/IOException;)Z");

    jclass postProcess_class = FindClassOrDie(env, "android/graphics/PostProcess");
    gPostProcess_postProcessMethodID = GetMethodIDOrDie(env, postProcess_class, "postProcess", "(Landroid/graphics/Canvas;II)I");
+218 −116

File changed.

Preview size limit exceeded, changes collapsed.