| #include "CreateJavaOutputStreamAdaptor.h" |
| #include "SkData.h" |
| #include "SkRefCnt.h" |
| #include "SkStream.h" |
| #include "SkTypes.h" |
| #include "Utils.h" |
| |
| #include <cstdlib> |
| #include <nativehelper/JNIHelp.h> |
| #include <log/log.h> |
| #include <memory> |
| |
| static jmethodID gInputStream_readMethodID; |
| static jmethodID gInputStream_skipMethodID; |
| |
| /** |
| * 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: |
| 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; |
| } |
| |
| jint capacity = env->GetArrayLength(ar); |
| return new JavaInputStreamAdaptor(jvm, js, ar, capacity, swallowExceptions); |
| } |
| |
| ~JavaInputStreamAdaptor() override { |
| auto* env = android::requireEnv(fJvm); |
| env->DeleteGlobalRef(fJavaInputStream); |
| env->DeleteGlobalRef(fJavaByteArray); |
| } |
| |
| size_t read(void* buffer, size_t size) override { |
| auto* env = android::requireEnv(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; |
| } 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, env); |
| if (0 == amount) { |
| char tmp; |
| amount = this->doRead(&tmp, 1, env); |
| 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, env); |
| } |
| |
| bool isAtEnd() const override { return fIsAtEnd; } |
| |
| private: |
| size_t doRead(void* buffer, size_t size, JNIEnv* env) { |
| size_t bytesRead = 0; |
| // read the bytes |
| do { |
| jint requested = 0; |
| if (size > static_cast<size_t>(fCapacity)) { |
| requested = fCapacity; |
| } else { |
| // This is safe because requested is clamped to (jint) |
| // fCapacity. |
| requested = static_cast<jint>(size); |
| } |
| |
| jint n = env->CallIntMethod(fJavaInputStream, |
| gInputStream_readMethodID, fJavaByteArray, 0, requested); |
| if (checkException(env)) { |
| ALOGD("---- read threw an exception\n"); |
| return bytesRead; |
| } |
| |
| if (n < 0) { // n == 0 should not be possible, see InputStream read() specifications. |
| fIsAtEnd = true; |
| break; // eof |
| } |
| |
| env->GetByteArrayRegion(fJavaByteArray, 0, n, |
| reinterpret_cast<jbyte*>(buffer)); |
| if (checkException(env)) { |
| ALOGD("---- read:GetByteArrayRegion threw an exception\n"); |
| return bytesRead; |
| } |
| |
| buffer = (void*)((char*)buffer + n); |
| bytesRead += n; |
| size -= n; |
| fBytesRead += n; |
| } while (size != 0); |
| |
| return bytesRead; |
| } |
| |
| size_t doSkip(size_t size, JNIEnv* env) { |
| jlong skipped = env->CallLongMethod(fJavaInputStream, |
| gInputStream_skipMethodID, (jlong)size); |
| if (checkException(env)) { |
| ALOGD("------- skip threw an exception\n"); |
| return 0; |
| } |
| if (skipped < 0) { |
| skipped = 0; |
| } |
| |
| return (size_t)skipped; |
| } |
| |
| 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, |
| bool swallowExceptions) { |
| return JavaInputStreamAdaptor::Create(env, stream, storage, swallowExceptions); |
| } |
| |
| static void free_pointer_skproc(const void* ptr, void*) { |
| free((void*)ptr); |
| } |
| |
| sk_sp<SkData> CopyJavaInputStream(JNIEnv* env, jobject inputStream, jbyteArray storage) { |
| std::unique_ptr<SkStream> stream(CreateJavaInputStreamAdaptor(env, inputStream, storage)); |
| if (!stream) { |
| return nullptr; |
| } |
| |
| size_t bufferSize = 4096; |
| size_t streamLen = 0; |
| size_t len; |
| char* data = (char*)malloc(bufferSize); |
| LOG_ALWAYS_FATAL_IF(!data); |
| |
| while ((len = stream->read(data + streamLen, |
| bufferSize - streamLen)) != 0) { |
| streamLen += len; |
| if (streamLen == bufferSize) { |
| bufferSize *= 2; |
| data = (char*)realloc(data, bufferSize); |
| LOG_ALWAYS_FATAL_IF(!data); |
| } |
| } |
| if (streamLen == 0) { |
| // realloc with size 0 is unspecified behavior in C++11 |
| free(data); |
| data = nullptr; |
| } else { |
| // Trim down the buffer to the actual size of the data. |
| LOG_FATAL_IF(streamLen > bufferSize); |
| data = (char*)realloc(data, streamLen); |
| LOG_ALWAYS_FATAL_IF(!data); |
| } |
| // Just in case sk_free differs from free, we ask Skia to use |
| // free to cleanup the buffer that SkData wraps. |
| return SkData::MakeWithProc(data, streamLen, free_pointer_skproc, nullptr); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| static jmethodID gOutputStream_writeMethodID; |
| static jmethodID gOutputStream_flushMethodID; |
| |
| class SkJavaOutputStream : public SkWStream { |
| public: |
| SkJavaOutputStream(JNIEnv* env, jobject stream, jbyteArray storage) |
| : fEnv(env), fJavaOutputStream(stream), fJavaByteArray(storage), fBytesWritten(0) { |
| fCapacity = env->GetArrayLength(storage); |
| } |
| |
| virtual size_t bytesWritten() const { |
| return fBytesWritten; |
| } |
| |
| virtual bool write(const void* buffer, size_t size) { |
| JNIEnv* env = fEnv; |
| jbyteArray storage = fJavaByteArray; |
| |
| while (size > 0) { |
| jint requested = 0; |
| if (size > static_cast<size_t>(fCapacity)) { |
| requested = fCapacity; |
| } else { |
| // This is safe because requested is clamped to (jint) |
| // fCapacity. |
| requested = static_cast<jint>(size); |
| } |
| |
| env->SetByteArrayRegion(storage, 0, requested, |
| reinterpret_cast<const jbyte*>(buffer)); |
| if (env->ExceptionCheck()) { |
| env->ExceptionDescribe(); |
| env->ExceptionClear(); |
| ALOGD("--- write:SetByteArrayElements threw an exception\n"); |
| return false; |
| } |
| |
| fEnv->CallVoidMethod(fJavaOutputStream, gOutputStream_writeMethodID, |
| storage, 0, requested); |
| if (env->ExceptionCheck()) { |
| env->ExceptionDescribe(); |
| env->ExceptionClear(); |
| ALOGD("------- write threw an exception\n"); |
| return false; |
| } |
| |
| buffer = (void*)((char*)buffer + requested); |
| size -= requested; |
| fBytesWritten += requested; |
| } |
| return true; |
| } |
| |
| virtual void flush() { |
| fEnv->CallVoidMethod(fJavaOutputStream, gOutputStream_flushMethodID); |
| } |
| |
| private: |
| JNIEnv* fEnv; |
| jobject fJavaOutputStream; // the caller owns this object |
| jbyteArray fJavaByteArray; // the caller owns this object |
| jint fCapacity; |
| size_t fBytesWritten; |
| }; |
| |
| SkWStream* CreateJavaOutputStreamAdaptor(JNIEnv* env, jobject stream, |
| jbyteArray storage) { |
| return new SkJavaOutputStream(env, stream, storage); |
| } |
| |
| static jclass findClassCheck(JNIEnv* env, const char classname[]) { |
| jclass clazz = env->FindClass(classname); |
| SkASSERT(!env->ExceptionCheck()); |
| return clazz; |
| } |
| |
| static jmethodID getMethodIDCheck(JNIEnv* env, jclass clazz, |
| const char methodname[], const char type[]) { |
| jmethodID id = env->GetMethodID(clazz, methodname, type); |
| SkASSERT(!env->ExceptionCheck()); |
| return id; |
| } |
| |
| int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env) { |
| jclass inputStream_Clazz = findClassCheck(env, "java/io/InputStream"); |
| gInputStream_readMethodID = getMethodIDCheck(env, inputStream_Clazz, "read", "([BII)I"); |
| gInputStream_skipMethodID = getMethodIDCheck(env, inputStream_Clazz, "skip", "(J)J"); |
| |
| jclass outputStream_Clazz = findClassCheck(env, "java/io/OutputStream"); |
| gOutputStream_writeMethodID = getMethodIDCheck(env, outputStream_Clazz, "write", "([BII)V"); |
| gOutputStream_flushMethodID = getMethodIDCheck(env, outputStream_Clazz, "flush", "()V"); |
| |
| return 0; |
| } |