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

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

Replace stream wrap-function w/ more specific ones

The current stream wrapper returns a potentially incorrect
value for a call to getLength(), is typically copied into
another stream (not always in the same way), and doesn't
always take advantage of its underlying data (like when it
is an Asset). The overall goal of this CL is to provide the
caller with something that is ready to use, depending on
what is asked for. If a copy is desired, the copy is made
before being returned to the caller.

core/jni/android/graphics/Bitmap.cpp:

    Include SkStream.h, since it is no longer included by
    CreateJavaOutputStreamAdaptor's header file.

core/jni/android/graphics/BitmapFactory.cpp:

    Pass an SkStreamRewindable to decoding functions, as Skia
    decoders will be updated to only take an SkStreamRewindable
    (which makes more sense because they require rewinding).

    Call the more specific GetRewindableStream to get a
    rewindable stream.

    Remove copyAssetToStream which has been moved to Utils.

    In nativeDecodeAsset, pass forcePurgeable as allowPurgeable
    in doDecode. Technically the old code worked, but it checked
    the BitmapOptions again.

    Remove getFDSize, which is no longer used.

core/jni/android/graphics/BitmapRegionDecoder.cpp:

    Remove redundant buildSkMemoryStream. nativeNewInstanceFromStream
    now calls CopyJavaInputStream, which handles the copy.

    Copy the Asset directly, using common code, rather than creating
    an AssetStreamAdaptor to copy.

core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp:
core/jni/android/graphics/CreateJavaOutputStreamAdaptor.h:

    Provide new interfaces to access data from a Java InputStream.
    The new interfaces are more specific about what type of stream
    is desired.

    Use forward declarations where possible.

    Remove doSize, which gives a misleading answer to the question
    of how long the entire stream is.

    TODO: Only call FindClass etc once.

core/jni/android/graphics/Movie.cpp:

    Check for an asset stream, and use it if possible. Then call
    GetRewindableStream if there is not an asset.
    Remove the memory leak. Call DeleteLocalRef to delete the
    allocated memory.

core/jni/android/graphics/Picture.cpp:

    Call the new interface.

core/jni/android/graphics/Utils.cpp:
core/jni/android/graphics/Utils.h:

    Make AssetStreamAdaptor inherit from SkStreamRewindable so it
    can be passed to Skia decoding functions once they require it.

    Add CopyAssetToStream (moved from BitmapFactory.cpp) so it can
    be used by multiple files.

graphics/java/android/graphics/BitmapFactory.java:

    Remove the call to mark, which is now done natively.

    Remove the BufferedInputStream. Mark/reset is now handled
    by native code.

    Allow decodeStream to handle a FileInputStream by using the
    FileDescriptor, if it is seekable. In decodeFileDescriptor,
    call nativeDecodeStream instead of decodeStream so this new
    functionality will not loop.

    Call setDensityFromOptions in decodeFileDescriptor.

graphics/java/android/graphics/BitmapRegionDecoder.java:

    Remove the BufferedInputStream. Mark/reset is now handled
    by native code.

TODO: ADD TESTS!

Requires https://googleplex-android-review.googlesource.com/#/c/344317/

BUG=https://b.corp.google.com/issue?id=8432093

Change-Id: I4419b70b3482325c98ecc673dbfc4613f1b18581
parent fead1290
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@
#include "GraphicsJNI.h"
#include "SkDither.h"
#include "SkUnPreMultiply.h"
#include "SkStream.h"

#include <binder/Parcel.h>
#include "android_os_Parcel.h"
+9 −46
Original line number Diff line number Diff line
@@ -202,7 +202,7 @@ private:
// since we "may" create a purgeable imageref, we require the stream be ref'able
// i.e. dynamically allocated, since its lifetime may exceed the current stack
// frame.
static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,
static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding,
        jobject options, bool allowPurgeable, bool forcePurgeable = false) {

    int sampleSize = 1;
@@ -459,26 +459,17 @@ static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteA
        jobject padding, jobject options) {

    jobject bitmap = NULL;
    SkStream* stream = CreateJavaInputStreamAdaptor(env, is, storage, 0);
    SkAutoTUnref<SkStreamRewindable> stream(GetRewindableStream(env, is, storage));

    if (stream) {
    if (stream.get()) {
        // for now we don't allow purgeable with java inputstreams
        // FIXME: GetRewindableStream may have made a copy, in which case
        // purgeable should be allowed.
        bitmap = doDecode(env, stream, padding, options, false, false);
        stream->unref();
    }
    return bitmap;
}

static ssize_t getFDSize(int fd) {
    off64_t curr = ::lseek64(fd, 0, SEEK_CUR);
    if (curr < 0) {
        return 0;
    }
    size_t size = ::lseek(fd, 0, SEEK_END);
    ::lseek64(fd, curr, SEEK_SET);
    return size;
}

static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz, jobject fileDescriptor,
        jobject padding, jobject bitmapFactoryOptions) {

@@ -512,44 +503,16 @@ static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz, jobject fi
    return doDecode(env, stream, padding, bitmapFactoryOptions, weOwnTheFD);
}

/*  make a deep copy of the asset, and return it as a stream, or NULL if there
    was an error.
 */
static SkStream* copyAssetToStream(Asset* asset) {
    // if we could "ref/reopen" the asset, we may not need to copy it here
    off64_t size = asset->seek(0, SEEK_SET);
    if ((off64_t)-1 == size) {
        SkDebugf("---- copyAsset: asset rewind failed\n");
        return NULL;
    }

    size = asset->getLength();
    if (size <= 0) {
        SkDebugf("---- copyAsset: asset->getLength() returned %d\n", size);
        return NULL;
    }

    SkStream* stream = new SkMemoryStream(size);
    void* data = const_cast<void*>(stream->getMemoryBase());
    off64_t len = asset->read(data, size);
    if (len != size) {
        SkDebugf("---- copyAsset: asset->read(%d) returned %d\n", size, len);
        delete stream;
        stream = NULL;
    }
    return stream;
}

static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz, jint native_asset,
        jobject padding, jobject options) {

    SkStream* stream;
    SkStreamRewindable* stream;
    Asset* asset = reinterpret_cast<Asset*>(native_asset);
    bool forcePurgeable = optionsPurgeable(env, options);
    if (forcePurgeable) {
        // if we could "ref/reopen" the asset, we may not need to copy it here
        // and we could assume optionsShareable, since assets are always RO
        stream = copyAssetToStream(asset);
        stream = CopyAssetToStream(asset);
        if (stream == NULL) {
            return NULL;
        }
@@ -559,7 +522,7 @@ static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz, jint native_asset,
        stream = new AssetStreamAdaptor(asset);
    }
    SkAutoUnref aur(stream);
    return doDecode(env, stream, padding, options, true, forcePurgeable);
    return doDecode(env, stream, padding, options, forcePurgeable, forcePurgeable);
}

static jobject nativeDecodeByteArray(JNIEnv* env, jobject, jbyteArray byteArray,
@@ -572,7 +535,7 @@ static jobject nativeDecodeByteArray(JNIEnv* env, jobject, jbyteArray byteArray,
     */
    bool purgeable = optionsPurgeable(env, options) && !optionsJustBounds(env, options);
    AutoJavaByteArray ar(env, byteArray);
    SkStream* stream = new SkMemoryStream(ar.ptr() + offset, length, purgeable);
    SkMemoryStream* stream = new SkMemoryStream(ar.ptr() + offset, length, purgeable);
    SkAutoUnref aur(stream);
    return doDecode(env, stream, NULL, options, purgeable);
}
+10 −33
Original line number Diff line number Diff line
@@ -76,27 +76,6 @@ private:
    int fHeight;
};

static SkMemoryStream* buildSkMemoryStream(SkStream *stream) {
    size_t bufferSize = 4096;
    size_t streamLen = 0;
    size_t len;
    char* data = (char*)sk_malloc_throw(bufferSize);

    while ((len = stream->read(data + streamLen,
                    bufferSize - streamLen)) != 0) {
        streamLen += len;
        if (streamLen == bufferSize) {
            bufferSize *= 2;
            data = (char*)sk_realloc_throw(data, bufferSize);
        }
    }
    data = (char*)sk_realloc_throw(data, streamLen);

    SkMemoryStream* streamMem = new SkMemoryStream();
    streamMem->setMemoryOwned(data, streamLen);
    return streamMem;
}

static jobject createBitmapRegionDecoder(JNIEnv* env, SkStream* stream) {
    SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
    int width, height;
@@ -161,14 +140,12 @@ static jobject nativeNewInstanceFromStream(JNIEnv* env, jobject clazz,
                                  jbyteArray storage, // byte[]
                                  jboolean isShareable) {
    jobject brd = NULL;
    SkStream* stream = CreateJavaInputStreamAdaptor(env, is, storage, 1024);
    // for now we don't allow shareable with java inputstreams
    SkStream* stream = CopyJavaInputStream(env, is, storage);

    if (stream) {
        // for now we don't allow shareable with java inputstreams
        SkMemoryStream* mStream = buildSkMemoryStream(stream);
        brd = createBitmapRegionDecoder(env, mStream);
        SkSafeUnref(mStream); // the decoder now holds a reference
        stream->unref();
        brd = createBitmapRegionDecoder(env, stream);
        stream->unref(); // the decoder now holds a reference
    }
    return brd;
}
@@ -176,14 +153,14 @@ static jobject nativeNewInstanceFromStream(JNIEnv* env, jobject clazz,
static jobject nativeNewInstanceFromAsset(JNIEnv* env, jobject clazz,
                                 jint native_asset, // Asset
                                 jboolean isShareable) {
    SkStream* stream, *assStream;
    Asset* asset = reinterpret_cast<Asset*>(native_asset);
    assStream = new AssetStreamAdaptor(asset);
    stream = buildSkMemoryStream(assStream);
    assStream->unref();
    SkAutoTUnref<SkMemoryStream> stream(CopyAssetToStream(asset));
    if (NULL == stream.get()) {
        return NULL;
    }

    jobject brd = createBitmapRegionDecoder(env, stream);
    SkSafeUnref(stream); // the decoder now holds a reference
    jobject brd = createBitmapRegionDecoder(env, stream.get());
    // The decoder now holds a reference to stream.
    return brd;
}

+221 −55
Original line number Diff line number Diff line
#include "CreateJavaOutputStreamAdaptor.h"
#include "JNIHelp.h"
#include "SkData.h"
#include "SkRefCnt.h"
#include "SkStream.h"
#include "SkTypes.h"
#include "Utils.h"
#include <androidfw/Asset.h>

#define RETURN_NULL_IF_NULL(value) \
    do { if (!(value)) { SkASSERT(0); return NULL; } } while (false)

#define RETURN_ZERO_IF_NULL(value) \
    do { if (!(value)) { SkASSERT(0); return 0; } } while (false)

static jmethodID    gInputStream_resetMethodID;
static jmethodID    gInputStream_markMethodID;
static jmethodID    gInputStream_availableMethodID;
static jmethodID    gInputStream_markSupportedMethodID;
static jmethodID    gInputStream_readMethodID;
static jmethodID    gInputStream_skipMethodID;

class RewindableJavaStream;

/**
 *  Non-rewindable wrapper for a Java InputStream.
 */
class JavaInputStreamAdaptor : public SkStream {
public:
    JavaInputStreamAdaptor(JNIEnv* env, jobject js, jbyteArray ar)
@@ -17,12 +32,52 @@ public:
        fCapacity = env->GetArrayLength(ar);
        SkASSERT(fCapacity > 0);
        fBytesRead = 0;
        fIsAtEnd = false;
    }

	virtual bool rewind() {
    virtual size_t read(void* buffer, size_t size) {
        JNIEnv* env = fEnv;
        if (NULL == buffer) {
            if (0 == size) {
                return 0;
            } else {
                /*  InputStream.skip(n) can return <=0 but still not be at EOF
                    If we see that value, we need to call read(), which will
                    block if waiting for more data, or return -1 at EOF
                 */
                size_t amountSkipped = 0;
                do {
                    size_t amount = this->doSkip(size - amountSkipped);
                    if (0 == amount) {
                        char tmp;
                        amount = this->doRead(&tmp, 1);
                        if (0 == amount) {
                            // if read returned 0, we're at EOF
                            fIsAtEnd = true;
                            break;
                        }
                    }
                    amountSkipped += amount;
                } while (amountSkipped < size);
                return amountSkipped;
            }
        }
        return this->doRead(buffer, size);
    }

    virtual bool isAtEnd() const {
        return fIsAtEnd;
    }

private:
    // Does not override rewind, since a JavaInputStreamAdaptor's interface
    // does not support rewinding. RewindableJavaStream, which is a friend,
    // will be able to call this method to rewind.
    bool doRewind() {
        JNIEnv* env = fEnv;

        fBytesRead = 0;
        fIsAtEnd = false;

        env->CallVoidMethod(fJavaInputStream, gInputStream_resetMethodID);
        if (env->ExceptionCheck()) {
@@ -53,6 +108,7 @@ public:
            }

            if (n < 0) { // n == 0 should not be possible, see InputStream read() specifications.
                fIsAtEnd = true;
                break;  // eof
            }

@@ -92,58 +148,19 @@ public:
        return (size_t)skipped;
    }

    size_t doSize() {
        JNIEnv* env = fEnv;
        jint avail = env->CallIntMethod(fJavaInputStream,
                                        gInputStream_availableMethodID);
        if (env->ExceptionCheck()) {
            env->ExceptionDescribe();
            env->ExceptionClear();
            SkDebugf("------- available threw an exception\n");
            avail = 0;
        }
        return avail;
    }

	virtual size_t read(void* buffer, size_t size) {
        JNIEnv* env = fEnv;
        if (NULL == buffer) {
            if (0 == size) {
                return this->doSize();
            } else {
                /*  InputStream.skip(n) can return <=0 but still not be at EOF
                    If we see that value, we need to call read(), which will
                    block if waiting for more data, or return -1 at EOF
                 */
                size_t amountSkipped = 0;
                do {
                    size_t amount = this->doSkip(size - amountSkipped);
                    if (0 == amount) {
                        char tmp;
                        amount = this->doRead(&tmp, 1);
                        if (0 == amount) {
                            // if read returned 0, we're at EOF
                            break;
                        }
                    }
                    amountSkipped += amount;
                } while (amountSkipped < size);
                return amountSkipped;
            }
        }
        return this->doRead(buffer, size);
    }

private:
    JNIEnv*     fEnv;
    jobject     fJavaInputStream;   // the caller owns this object
    jbyteArray  fJavaByteArray;     // the caller owns this object
    size_t      fCapacity;
    size_t      fBytesRead;
    bool        fIsAtEnd;

    // Allows access to doRewind and fBytesRead.
    friend class RewindableJavaStream;
};

SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream,
                                       jbyteArray storage, int markSize) {
SkStream* WrapJavaInputStream(JNIEnv* env, jobject stream,
                              jbyteArray storage) {
    static bool gInited;

    if (!gInited) {
@@ -154,8 +171,8 @@ SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream,
                                                           "reset", "()V");
        gInputStream_markMethodID       = env->GetMethodID(inputStream_Clazz,
                                                           "mark", "(I)V");
        gInputStream_availableMethodID  = env->GetMethodID(inputStream_Clazz,
                                                           "available", "()I");
        gInputStream_markSupportedMethodID = env->GetMethodID(inputStream_Clazz,
                                                              "markSupported", "()Z");
        gInputStream_readMethodID       = env->GetMethodID(inputStream_Clazz,
                                                           "read", "([BII)I");
        gInputStream_skipMethodID       = env->GetMethodID(inputStream_Clazz,
@@ -163,18 +180,167 @@ SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream,

        RETURN_NULL_IF_NULL(gInputStream_resetMethodID);
        RETURN_NULL_IF_NULL(gInputStream_markMethodID);
        RETURN_NULL_IF_NULL(gInputStream_availableMethodID);
        RETURN_NULL_IF_NULL(gInputStream_markSupportedMethodID);
        RETURN_NULL_IF_NULL(gInputStream_readMethodID);
        RETURN_NULL_IF_NULL(gInputStream_skipMethodID);

        gInited = true;
    }

    if (markSize) {
        env->CallVoidMethod(stream, gInputStream_markMethodID, markSize);
    return new JavaInputStreamAdaptor(env, stream, storage);
}

    return new JavaInputStreamAdaptor(env, stream, storage);
static SkMemoryStream* adaptor_to_mem_stream(SkStream* adaptor) {
    SkASSERT(adaptor != NULL);
    SkDynamicMemoryWStream wStream;
    const int bufferSize = 256 * 1024; // 256 KB, same as ViewStateSerializer.
    uint8_t buffer[bufferSize];
    do {
        size_t bytesRead = adaptor->read(buffer, bufferSize);
        wStream.write(buffer, bytesRead);
    } while (!adaptor->isAtEnd());
    SkAutoTUnref<SkData> data(wStream.copyToData());
    return new SkMemoryStream(data.get());
}

SkMemoryStream* CopyJavaInputStream(JNIEnv* env, jobject stream,
                                    jbyteArray storage) {
    SkAutoTUnref<SkStream> adaptor(WrapJavaInputStream(env, stream, storage));
    if (NULL == adaptor.get()) {
        return NULL;
    }
    return adaptor_to_mem_stream(adaptor.get());
}

/**
 *  Wrapper for a Java InputStream which is rewindable and
 *  has a length.
 */
class RewindableJavaStream : public SkStreamRewindable {
public:
    // RewindableJavaStream takes ownership of adaptor.
    RewindableJavaStream(JavaInputStreamAdaptor* adaptor, size_t length)
        : fAdaptor(adaptor)
        , fLength(length) {
        SkASSERT(fAdaptor != NULL);
    }

    virtual ~RewindableJavaStream() {
        fAdaptor->unref();
    }

    virtual bool rewind() {
        return fAdaptor->doRewind();
    }

    virtual size_t read(void* buffer, size_t size) {
        return fAdaptor->read(buffer, size);
    }

    virtual bool isAtEnd() const {
        return fAdaptor->isAtEnd();
    }

    virtual size_t getLength() const {
        return fLength;
    }

    virtual bool hasLength() const {
        return true;
    }

    virtual SkStreamRewindable* duplicate() const {
        // Duplicating this stream requires rewinding and
        // reading, which modify this Stream (and could
        // fail, leaving this one invalid).
        SkASSERT(false);
        return NULL;
    }

private:
    JavaInputStreamAdaptor* fAdaptor;
    const size_t            fLength;
};

/**
 *  If jstream is a ByteArrayInputStream, return its remaining length. Otherwise
 *  return 0.
 */
static size_t get_length_from_byte_array_stream(JNIEnv* env, jobject jstream) {
    static jclass byteArrayInputStream_Clazz;
    static jfieldID countField;
    static jfieldID posField;

    byteArrayInputStream_Clazz = env->FindClass("java/io/ByteArrayInputStream");
    RETURN_ZERO_IF_NULL(byteArrayInputStream_Clazz);

    countField = env->GetFieldID(byteArrayInputStream_Clazz, "count", "I");
    RETURN_ZERO_IF_NULL(byteArrayInputStream_Clazz);
    posField = env->GetFieldID(byteArrayInputStream_Clazz, "pos", "I");
    RETURN_ZERO_IF_NULL(byteArrayInputStream_Clazz);

    if (env->IsInstanceOf(jstream, byteArrayInputStream_Clazz)) {
        // Return the remaining length, to keep the same behavior of using the rest of the
        // stream.
        return env->GetIntField(jstream, countField) - env->GetIntField(jstream, posField);
    }
    return 0;
}

/**
 *  If jstream is a class that has a length, return it. Otherwise
 *  return 0.
 *  Only checks for a set of subclasses.
 */
static size_t get_length_if_supported(JNIEnv* env, jobject jstream) {
    size_t len = get_length_from_byte_array_stream(env, jstream);
    if (len > 0) {
        return len;
    }
    return 0;
}

SkStreamRewindable* GetRewindableStream(JNIEnv* env, jobject stream,
                                        jbyteArray storage) {
    SkAutoTUnref<SkStream> adaptor(WrapJavaInputStream(env, stream, storage));
    if (NULL == adaptor.get()) {
        return NULL;
    }

    const size_t length = get_length_if_supported(env, stream);
    if (length > 0 && env->CallBooleanMethod(stream, gInputStream_markSupportedMethodID)) {
        // Set the readLimit for mark to the end of the stream, so it can
        // be rewound regardless of how much has been read.
        env->CallVoidMethod(stream, gInputStream_markMethodID, length);
        // RewindableJavaStream will unref adaptor when it is destroyed.
        return new RewindableJavaStream(static_cast<JavaInputStreamAdaptor*>(adaptor.detach()),
                                        length);
    }

    return adaptor_to_mem_stream(adaptor.get());
}

android::AssetStreamAdaptor* CheckForAssetStream(JNIEnv* env, jobject jstream) {
    static jclass assetInputStream_Clazz;
    static jmethodID getAssetIntMethodID;

    assetInputStream_Clazz = env->FindClass("android/content/res/AssetManager$AssetInputStream");
    RETURN_NULL_IF_NULL(assetInputStream_Clazz);

    getAssetIntMethodID = env->GetMethodID(assetInputStream_Clazz, "getAssetInt", "()I");
    RETURN_NULL_IF_NULL(getAssetIntMethodID);

    if (!env->IsInstanceOf(jstream, assetInputStream_Clazz)) {
        return NULL;
    }

    jint jasset = env->CallIntMethod(jstream, getAssetIntMethodID);
    android::Asset* a = reinterpret_cast<android::Asset*>(jasset);
    if (NULL == a) {
        jniThrowNullPointerException(env, "NULL native asset");
        return NULL;
    }
    return new android::AssetStreamAdaptor(a);
}

///////////////////////////////////////////////////////////////////////////////
+63 −3
Original line number Diff line number Diff line
@@ -3,10 +3,70 @@

//#include <android_runtime/AndroidRuntime.h>
#include "jni.h"
#include "SkStream.h"

SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream,
                                       jbyteArray storage, int markSize = 0);
namespace android {
    class AssetStreamAdaptor;
}

class SkMemoryStream;
class SkStream;
class SkStreamRewindable;
class SkWStream;

/**
 *  Return an adaptor from a Java InputStream to an SkStream.
 *  @param env JNIEnv object.
 *  @param stream Pointer to Java InputStream.
 *  @param storage Java byte array for retrieving data from the
 *      Java InputStream.
 *  @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* WrapJavaInputStream(JNIEnv* env, jobject stream,
                              jbyteArray storage);

/**
 *  Copy a Java InputStream.
 *  @param env JNIEnv object.
 *  @param stream Pointer to Java InputStream.
 *  @param storage Java byte array for retrieving data from the
 *      Java InputStream.
 *  @return SkMemoryStream The data in stream will be copied to a new
 *      SkMemoryStream.
 *  FIXME: Could return a more generic return type if ViewStateSerializer
 *  did not require an SkMemoryStream.
 */
SkMemoryStream* CopyJavaInputStream(JNIEnv* env, jobject stream,
                                    jbyteArray storage);

/**
 *  Get a rewindable stream from a Java InputStream.
 *  @param env JNIEnv object.
 *  @param stream Pointer to Java InputStream.
 *  @param storage Java byte array for retrieving data from the
 *      Java InputStream.
 *  @return SkStreamRewindable Either a wrapper around the Java
 *      InputStream, if possible, or a copy which is rewindable.
 *      Since it may be a wrapper, must not be used after the
 *      caller returns, like the result of WrapJavaInputStream.
 */
SkStreamRewindable* GetRewindableStream(JNIEnv* env, jobject stream,
                                        jbyteArray storage);

/**
 *  If the Java InputStream is an AssetInputStream, return an adaptor.
 *  This should not be used after the calling function returns, since
 *  the caller may close the asset. Returns NULL if the stream is
 *  not an AssetInputStream.
 *  @param env JNIEnv object.
 *  @param stream Pointer to Java InputStream.
 *  @return AssetStreamAdaptor representing the InputStream, or NULL.
 *      Must not be held onto.
 */
android::AssetStreamAdaptor* CheckForAssetStream(JNIEnv* env, jobject stream);

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

Loading