1 #include "CreateJavaOutputStreamAdaptor.h" 2 #include "SkData.h" 3 #include "SkMalloc.h" 4 #include "SkRefCnt.h" 5 #include "SkStream.h" 6 #include "SkTypes.h" 7 #include "Utils.h" 8 9 #include <nativehelper/JNIHelp.h> 10 #include <memory> 11 12 static jmethodID gInputStream_readMethodID; 13 static jmethodID gInputStream_skipMethodID; 14 15 /** 16 * Wrapper for a Java InputStream. 17 */ 18 class JavaInputStreamAdaptor : public SkStream { 19 JavaInputStreamAdaptor(JavaVM* jvm, jobject js, jbyteArray ar, jint capacity, 20 bool swallowExceptions) 21 : fJvm(jvm) 22 , fJavaInputStream(js) 23 , fJavaByteArray(ar) 24 , fCapacity(capacity) 25 , fBytesRead(0) 26 , fIsAtEnd(false) 27 , fSwallowExceptions(swallowExceptions) {} 28 29 public: 30 static JavaInputStreamAdaptor* Create(JNIEnv* env, jobject js, jbyteArray ar, 31 bool swallowExceptions) { 32 JavaVM* jvm; 33 LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&jvm) != JNI_OK); 34 35 js = env->NewGlobalRef(js); 36 if (!js) { 37 return nullptr; 38 } 39 40 ar = (jbyteArray) env->NewGlobalRef(ar); 41 if (!ar) { 42 env->DeleteGlobalRef(js); 43 return nullptr; 44 } 45 46 jint capacity = env->GetArrayLength(ar); 47 return new JavaInputStreamAdaptor(jvm, js, ar, capacity, swallowExceptions); 48 } 49 50 ~JavaInputStreamAdaptor() override { 51 auto* env = android::get_env_or_die(fJvm); 52 env->DeleteGlobalRef(fJavaInputStream); 53 env->DeleteGlobalRef(fJavaByteArray); 54 } 55 56 size_t read(void* buffer, size_t size) override { 57 auto* env = android::get_env_or_die(fJvm); 58 if (!fSwallowExceptions && checkException(env)) { 59 // Just in case the caller did not clear from a previous exception. 60 return 0; 61 } 62 if (NULL == buffer) { 63 if (0 == size) { 64 return 0; 65 } else { 66 /* InputStream.skip(n) can return <=0 but still not be at EOF 67 If we see that value, we need to call read(), which will 68 block if waiting for more data, or return -1 at EOF 69 */ 70 size_t amountSkipped = 0; 71 do { 72 size_t amount = this->doSkip(size - amountSkipped, env); 73 if (0 == amount) { 74 char tmp; 75 amount = this->doRead(&tmp, 1, env); 76 if (0 == amount) { 77 // if read returned 0, we're at EOF 78 fIsAtEnd = true; 79 break; 80 } 81 } 82 amountSkipped += amount; 83 } while (amountSkipped < size); 84 return amountSkipped; 85 } 86 } 87 return this->doRead(buffer, size, env); 88 } 89 90 bool isAtEnd() const override { return fIsAtEnd; } 91 92 private: 93 size_t doRead(void* buffer, size_t size, JNIEnv* env) { 94 size_t bytesRead = 0; 95 // read the bytes 96 do { 97 jint requested = 0; 98 if (size > static_cast<size_t>(fCapacity)) { 99 requested = fCapacity; 100 } else { 101 // This is safe because requested is clamped to (jint) 102 // fCapacity. 103 requested = static_cast<jint>(size); 104 } 105 106 jint n = env->CallIntMethod(fJavaInputStream, 107 gInputStream_readMethodID, fJavaByteArray, 0, requested); 108 if (checkException(env)) { 109 SkDebugf("---- read threw an exception\n"); 110 return bytesRead; 111 } 112 113 if (n < 0) { // n == 0 should not be possible, see InputStream read() specifications. 114 fIsAtEnd = true; 115 break; // eof 116 } 117 118 env->GetByteArrayRegion(fJavaByteArray, 0, n, 119 reinterpret_cast<jbyte*>(buffer)); 120 if (checkException(env)) { 121 SkDebugf("---- read:GetByteArrayRegion threw an exception\n"); 122 return bytesRead; 123 } 124 125 buffer = (void*)((char*)buffer + n); 126 bytesRead += n; 127 size -= n; 128 fBytesRead += n; 129 } while (size != 0); 130 131 return bytesRead; 132 } 133 134 size_t doSkip(size_t size, JNIEnv* env) { 135 jlong skipped = env->CallLongMethod(fJavaInputStream, 136 gInputStream_skipMethodID, (jlong)size); 137 if (checkException(env)) { 138 SkDebugf("------- skip threw an exception\n"); 139 return 0; 140 } 141 if (skipped < 0) { 142 skipped = 0; 143 } 144 145 return (size_t)skipped; 146 } 147 148 bool checkException(JNIEnv* env) { 149 if (!env->ExceptionCheck()) { 150 return false; 151 } 152 153 env->ExceptionDescribe(); 154 if (fSwallowExceptions) { 155 env->ExceptionClear(); 156 } 157 158 // There is no way to recover from the error, so consider the stream 159 // to be at the end. 160 fIsAtEnd = true; 161 162 return true; 163 } 164 165 JavaVM* fJvm; 166 jobject fJavaInputStream; 167 jbyteArray fJavaByteArray; 168 const jint fCapacity; 169 size_t fBytesRead; 170 bool fIsAtEnd; 171 const bool fSwallowExceptions; 172 }; 173 174 SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream, jbyteArray storage, 175 bool swallowExceptions) { 176 return JavaInputStreamAdaptor::Create(env, stream, storage, swallowExceptions); 177 } 178 179 static SkMemoryStream* adaptor_to_mem_stream(SkStream* stream) { 180 SkASSERT(stream != NULL); 181 size_t bufferSize = 4096; 182 size_t streamLen = 0; 183 size_t len; 184 char* data = (char*)sk_malloc_throw(bufferSize); 185 186 while ((len = stream->read(data + streamLen, 187 bufferSize - streamLen)) != 0) { 188 streamLen += len; 189 if (streamLen == bufferSize) { 190 bufferSize *= 2; 191 data = (char*)sk_realloc_throw(data, bufferSize); 192 } 193 } 194 data = (char*)sk_realloc_throw(data, streamLen); 195 196 SkMemoryStream* streamMem = new SkMemoryStream(); 197 streamMem->setMemoryOwned(data, streamLen); 198 return streamMem; 199 } 200 201 SkStreamRewindable* CopyJavaInputStream(JNIEnv* env, jobject stream, 202 jbyteArray storage) { 203 std::unique_ptr<SkStream> adaptor(CreateJavaInputStreamAdaptor(env, stream, storage)); 204 if (NULL == adaptor.get()) { 205 return NULL; 206 } 207 return adaptor_to_mem_stream(adaptor.get()); 208 } 209 210 /////////////////////////////////////////////////////////////////////////////// 211 212 static jmethodID gOutputStream_writeMethodID; 213 static jmethodID gOutputStream_flushMethodID; 214 215 class SkJavaOutputStream : public SkWStream { 216 public: 217 SkJavaOutputStream(JNIEnv* env, jobject stream, jbyteArray storage) 218 : fEnv(env), fJavaOutputStream(stream), fJavaByteArray(storage), fBytesWritten(0) { 219 fCapacity = env->GetArrayLength(storage); 220 } 221 222 virtual size_t bytesWritten() const { 223 return fBytesWritten; 224 } 225 226 virtual bool write(const void* buffer, size_t size) { 227 JNIEnv* env = fEnv; 228 jbyteArray storage = fJavaByteArray; 229 230 while (size > 0) { 231 jint requested = 0; 232 if (size > static_cast<size_t>(fCapacity)) { 233 requested = fCapacity; 234 } else { 235 // This is safe because requested is clamped to (jint) 236 // fCapacity. 237 requested = static_cast<jint>(size); 238 } 239 240 env->SetByteArrayRegion(storage, 0, requested, 241 reinterpret_cast<const jbyte*>(buffer)); 242 if (env->ExceptionCheck()) { 243 env->ExceptionDescribe(); 244 env->ExceptionClear(); 245 SkDebugf("--- write:SetByteArrayElements threw an exception\n"); 246 return false; 247 } 248 249 fEnv->CallVoidMethod(fJavaOutputStream, gOutputStream_writeMethodID, 250 storage, 0, requested); 251 if (env->ExceptionCheck()) { 252 env->ExceptionDescribe(); 253 env->ExceptionClear(); 254 SkDebugf("------- write threw an exception\n"); 255 return false; 256 } 257 258 buffer = (void*)((char*)buffer + requested); 259 size -= requested; 260 fBytesWritten += requested; 261 } 262 return true; 263 } 264 265 virtual void flush() { 266 fEnv->CallVoidMethod(fJavaOutputStream, gOutputStream_flushMethodID); 267 } 268 269 private: 270 JNIEnv* fEnv; 271 jobject fJavaOutputStream; // the caller owns this object 272 jbyteArray fJavaByteArray; // the caller owns this object 273 jint fCapacity; 274 size_t fBytesWritten; 275 }; 276 277 SkWStream* CreateJavaOutputStreamAdaptor(JNIEnv* env, jobject stream, 278 jbyteArray storage) { 279 static bool gInited; 280 281 if (!gInited) { 282 283 gInited = true; 284 } 285 286 return new SkJavaOutputStream(env, stream, storage); 287 } 288 289 static jclass findClassCheck(JNIEnv* env, const char classname[]) { 290 jclass clazz = env->FindClass(classname); 291 SkASSERT(!env->ExceptionCheck()); 292 return clazz; 293 } 294 295 static jmethodID getMethodIDCheck(JNIEnv* env, jclass clazz, 296 const char methodname[], const char type[]) { 297 jmethodID id = env->GetMethodID(clazz, methodname, type); 298 SkASSERT(!env->ExceptionCheck()); 299 return id; 300 } 301 302 int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env) { 303 jclass inputStream_Clazz = findClassCheck(env, "java/io/InputStream"); 304 gInputStream_readMethodID = getMethodIDCheck(env, inputStream_Clazz, "read", "([BII)I"); 305 gInputStream_skipMethodID = getMethodIDCheck(env, inputStream_Clazz, "skip", "(J)J"); 306 307 jclass outputStream_Clazz = findClassCheck(env, "java/io/OutputStream"); 308 gOutputStream_writeMethodID = getMethodIDCheck(env, outputStream_Clazz, "write", "([BII)V"); 309 gOutputStream_flushMethodID = getMethodIDCheck(env, outputStream_Clazz, "flush", "()V"); 310 311 return 0; 312 } 313