blob: 39483b55992b4dfbc70779c98d4d9bbb86198d37 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001#include "CreateJavaOutputStreamAdaptor.h"
Leon Scroggins IIIca320212013-08-20 17:59:39 -04002#include "SkData.h"
Mike Reed31274c82018-01-05 10:25:08 -05003#include "SkMalloc.h"
Leon Scroggins IIIca320212013-08-20 17:59:39 -04004#include "SkRefCnt.h"
5#include "SkStream.h"
6#include "SkTypes.h"
7#include "Utils.h"
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008
Steven Moreland2279b252017-07-19 09:50:45 -07009#include <nativehelper/JNIHelp.h>
Derek Sollenbergereec1b862019-10-24 09:44:55 -040010#include <log/log.h>
Ben Wagner60126ef2015-08-07 12:13:48 -040011#include <memory>
12
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080013static jmethodID gInputStream_readMethodID;
14static jmethodID gInputStream_skipMethodID;
15
Leon Scroggins IIIca320212013-08-20 17:59:39 -040016/**
Leon Scroggins III7315f1b2013-09-10 20:26:05 -040017 * Wrapper for a Java InputStream.
Leon Scroggins IIIca320212013-08-20 17:59:39 -040018 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080019class JavaInputStreamAdaptor : public SkStream {
Leon Scroggins IIIed074fd2017-12-11 13:47:23 -050020 JavaInputStreamAdaptor(JavaVM* jvm, jobject js, jbyteArray ar, jint capacity,
21 bool swallowExceptions)
22 : fJvm(jvm)
23 , fJavaInputStream(js)
24 , fJavaByteArray(ar)
25 , fCapacity(capacity)
26 , fBytesRead(0)
27 , fIsAtEnd(false)
28 , fSwallowExceptions(swallowExceptions) {}
29
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030public:
Leon Scroggins IIIed074fd2017-12-11 13:47:23 -050031 static JavaInputStreamAdaptor* Create(JNIEnv* env, jobject js, jbyteArray ar,
32 bool swallowExceptions) {
33 JavaVM* jvm;
34 LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&jvm) != JNI_OK);
35
36 js = env->NewGlobalRef(js);
37 if (!js) {
38 return nullptr;
39 }
40
41 ar = (jbyteArray) env->NewGlobalRef(ar);
42 if (!ar) {
43 env->DeleteGlobalRef(js);
44 return nullptr;
45 }
46
47 jint capacity = env->GetArrayLength(ar);
48 return new JavaInputStreamAdaptor(jvm, js, ar, capacity, swallowExceptions);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080049 }
Elliott Hughesdd66bcb2011-04-12 11:28:59 -070050
Leon Scroggins IIIed074fd2017-12-11 13:47:23 -050051 ~JavaInputStreamAdaptor() override {
Leon Scroggins III127d31a2018-01-19 12:29:47 -050052 auto* env = android::get_env_or_die(fJvm);
Leon Scroggins IIIed074fd2017-12-11 13:47:23 -050053 env->DeleteGlobalRef(fJavaInputStream);
54 env->DeleteGlobalRef(fJavaByteArray);
55 }
56
57 size_t read(void* buffer, size_t size) override {
Leon Scroggins III127d31a2018-01-19 12:29:47 -050058 auto* env = android::get_env_or_die(fJvm);
Leon Scroggins IIIed074fd2017-12-11 13:47:23 -050059 if (!fSwallowExceptions && checkException(env)) {
60 // Just in case the caller did not clear from a previous exception.
61 return 0;
62 }
Leon Scroggins IIIca320212013-08-20 17:59:39 -040063 if (NULL == buffer) {
64 if (0 == size) {
65 return 0;
66 } else {
67 /* InputStream.skip(n) can return <=0 but still not be at EOF
68 If we see that value, we need to call read(), which will
69 block if waiting for more data, or return -1 at EOF
70 */
71 size_t amountSkipped = 0;
72 do {
Leon Scroggins IIIed074fd2017-12-11 13:47:23 -050073 size_t amount = this->doSkip(size - amountSkipped, env);
Leon Scroggins IIIca320212013-08-20 17:59:39 -040074 if (0 == amount) {
75 char tmp;
Leon Scroggins IIIed074fd2017-12-11 13:47:23 -050076 amount = this->doRead(&tmp, 1, env);
Leon Scroggins IIIca320212013-08-20 17:59:39 -040077 if (0 == amount) {
78 // if read returned 0, we're at EOF
79 fIsAtEnd = true;
80 break;
81 }
82 }
83 amountSkipped += amount;
84 } while (amountSkipped < size);
85 return amountSkipped;
86 }
87 }
Leon Scroggins IIIed074fd2017-12-11 13:47:23 -050088 return this->doRead(buffer, size, env);
Leon Scroggins IIIca320212013-08-20 17:59:39 -040089 }
90
Leon Scroggins IIIed074fd2017-12-11 13:47:23 -050091 bool isAtEnd() const override { return fIsAtEnd; }
Leon Scroggins IIIca320212013-08-20 17:59:39 -040092
93private:
Leon Scroggins IIIed074fd2017-12-11 13:47:23 -050094 size_t doRead(void* buffer, size_t size, JNIEnv* env) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080095 size_t bytesRead = 0;
96 // read the bytes
97 do {
Ashok Bhat2bb39d72014-03-05 12:40:53 +000098 jint requested = 0;
99 if (size > static_cast<size_t>(fCapacity)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800100 requested = fCapacity;
Ashok Bhat2bb39d72014-03-05 12:40:53 +0000101 } else {
102 // This is safe because requested is clamped to (jint)
103 // fCapacity.
104 requested = static_cast<jint>(size);
105 }
Elliott Hughesdd66bcb2011-04-12 11:28:59 -0700106
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800107 jint n = env->CallIntMethod(fJavaInputStream,
108 gInputStream_readMethodID, fJavaByteArray, 0, requested);
Leon Scroggins IIIed074fd2017-12-11 13:47:23 -0500109 if (checkException(env)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800110 SkDebugf("---- read threw an exception\n");
Leon Scroggins IIIed074fd2017-12-11 13:47:23 -0500111 return bytesRead;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800112 }
Elliott Hughesdd66bcb2011-04-12 11:28:59 -0700113
Gilles Debunne8cd48572010-07-15 18:06:36 -0700114 if (n < 0) { // n == 0 should not be possible, see InputStream read() specifications.
Leon Scroggins IIIca320212013-08-20 17:59:39 -0400115 fIsAtEnd = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800116 break; // eof
117 }
Elliott Hughesdd66bcb2011-04-12 11:28:59 -0700118
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800119 env->GetByteArrayRegion(fJavaByteArray, 0, n,
120 reinterpret_cast<jbyte*>(buffer));
Leon Scroggins IIIed074fd2017-12-11 13:47:23 -0500121 if (checkException(env)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800122 SkDebugf("---- read:GetByteArrayRegion threw an exception\n");
Leon Scroggins IIIed074fd2017-12-11 13:47:23 -0500123 return bytesRead;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800124 }
Elliott Hughesdd66bcb2011-04-12 11:28:59 -0700125
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800126 buffer = (void*)((char*)buffer + n);
127 bytesRead += n;
128 size -= n;
129 fBytesRead += n;
130 } while (size != 0);
Elliott Hughesdd66bcb2011-04-12 11:28:59 -0700131
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800132 return bytesRead;
133 }
Elliott Hughesdd66bcb2011-04-12 11:28:59 -0700134
Leon Scroggins IIIed074fd2017-12-11 13:47:23 -0500135 size_t doSkip(size_t size, JNIEnv* env) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800136 jlong skipped = env->CallLongMethod(fJavaInputStream,
137 gInputStream_skipMethodID, (jlong)size);
Leon Scroggins IIIed074fd2017-12-11 13:47:23 -0500138 if (checkException(env)) {
Gilles Debunne8cd48572010-07-15 18:06:36 -0700139 SkDebugf("------- skip threw an exception\n");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800140 return 0;
141 }
142 if (skipped < 0) {
143 skipped = 0;
144 }
Gilles Debunne8cd48572010-07-15 18:06:36 -0700145
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800146 return (size_t)skipped;
147 }
Elliott Hughesdd66bcb2011-04-12 11:28:59 -0700148
Leon Scroggins IIIed074fd2017-12-11 13:47:23 -0500149 bool checkException(JNIEnv* env) {
150 if (!env->ExceptionCheck()) {
151 return false;
152 }
153
154 env->ExceptionDescribe();
155 if (fSwallowExceptions) {
156 env->ExceptionClear();
157 }
158
159 // There is no way to recover from the error, so consider the stream
160 // to be at the end.
161 fIsAtEnd = true;
162
163 return true;
164 }
165
166 JavaVM* fJvm;
167 jobject fJavaInputStream;
168 jbyteArray fJavaByteArray;
169 const jint fCapacity;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800170 size_t fBytesRead;
Leon Scroggins IIIca320212013-08-20 17:59:39 -0400171 bool fIsAtEnd;
Leon Scroggins IIIed074fd2017-12-11 13:47:23 -0500172 const bool fSwallowExceptions;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800173};
174
Leon Scroggins IIIed074fd2017-12-11 13:47:23 -0500175SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream, jbyteArray storage,
176 bool swallowExceptions) {
177 return JavaInputStreamAdaptor::Create(env, stream, storage, swallowExceptions);
Leon Scroggins IIIca320212013-08-20 17:59:39 -0400178}
179
Leon Scroggins III37b82e32013-09-12 20:00:46 -0400180static SkMemoryStream* adaptor_to_mem_stream(SkStream* stream) {
181 SkASSERT(stream != NULL);
182 size_t bufferSize = 4096;
183 size_t streamLen = 0;
184 size_t len;
185 char* data = (char*)sk_malloc_throw(bufferSize);
186
187 while ((len = stream->read(data + streamLen,
188 bufferSize - streamLen)) != 0) {
189 streamLen += len;
190 if (streamLen == bufferSize) {
191 bufferSize *= 2;
192 data = (char*)sk_realloc_throw(data, bufferSize);
193 }
194 }
195 data = (char*)sk_realloc_throw(data, streamLen);
196
197 SkMemoryStream* streamMem = new SkMemoryStream();
198 streamMem->setMemoryOwned(data, streamLen);
199 return streamMem;
Leon Scroggins IIIca320212013-08-20 17:59:39 -0400200}
201
Leon Scroggins IIIc7797522013-09-03 11:35:00 -0400202SkStreamRewindable* CopyJavaInputStream(JNIEnv* env, jobject stream,
203 jbyteArray storage) {
Ben Wagner60126ef2015-08-07 12:13:48 -0400204 std::unique_ptr<SkStream> adaptor(CreateJavaInputStreamAdaptor(env, stream, storage));
Leon Scroggins IIIca320212013-08-20 17:59:39 -0400205 if (NULL == adaptor.get()) {
206 return NULL;
207 }
208 return adaptor_to_mem_stream(adaptor.get());
209}
210
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800211///////////////////////////////////////////////////////////////////////////////
212
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800213static jmethodID gOutputStream_writeMethodID;
214static jmethodID gOutputStream_flushMethodID;
215
216class SkJavaOutputStream : public SkWStream {
217public:
218 SkJavaOutputStream(JNIEnv* env, jobject stream, jbyteArray storage)
Leon Scrogginscc11f152014-03-31 16:52:13 -0400219 : fEnv(env), fJavaOutputStream(stream), fJavaByteArray(storage), fBytesWritten(0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800220 fCapacity = env->GetArrayLength(storage);
221 }
Elliott Hughesdd66bcb2011-04-12 11:28:59 -0700222
Leon Scrogginscc11f152014-03-31 16:52:13 -0400223 virtual size_t bytesWritten() const {
224 return fBytesWritten;
225 }
226
Ashok Bhat2bb39d72014-03-05 12:40:53 +0000227 virtual bool write(const void* buffer, size_t size) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800228 JNIEnv* env = fEnv;
229 jbyteArray storage = fJavaByteArray;
Elliott Hughesdd66bcb2011-04-12 11:28:59 -0700230
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800231 while (size > 0) {
Ashok Bhat2bb39d72014-03-05 12:40:53 +0000232 jint requested = 0;
233 if (size > static_cast<size_t>(fCapacity)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800234 requested = fCapacity;
Ashok Bhat2bb39d72014-03-05 12:40:53 +0000235 } else {
236 // This is safe because requested is clamped to (jint)
237 // fCapacity.
238 requested = static_cast<jint>(size);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800239 }
240
241 env->SetByteArrayRegion(storage, 0, requested,
242 reinterpret_cast<const jbyte*>(buffer));
243 if (env->ExceptionCheck()) {
244 env->ExceptionDescribe();
245 env->ExceptionClear();
246 SkDebugf("--- write:SetByteArrayElements threw an exception\n");
247 return false;
248 }
Elliott Hughesdd66bcb2011-04-12 11:28:59 -0700249
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800250 fEnv->CallVoidMethod(fJavaOutputStream, gOutputStream_writeMethodID,
251 storage, 0, requested);
252 if (env->ExceptionCheck()) {
253 env->ExceptionDescribe();
254 env->ExceptionClear();
255 SkDebugf("------- write threw an exception\n");
256 return false;
257 }
Elliott Hughesdd66bcb2011-04-12 11:28:59 -0700258
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800259 buffer = (void*)((char*)buffer + requested);
260 size -= requested;
Leon Scrogginscc11f152014-03-31 16:52:13 -0400261 fBytesWritten += requested;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800262 }
263 return true;
264 }
Elliott Hughesdd66bcb2011-04-12 11:28:59 -0700265
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800266 virtual void flush() {
267 fEnv->CallVoidMethod(fJavaOutputStream, gOutputStream_flushMethodID);
268 }
Elliott Hughesdd66bcb2011-04-12 11:28:59 -0700269
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800270private:
271 JNIEnv* fEnv;
272 jobject fJavaOutputStream; // the caller owns this object
273 jbyteArray fJavaByteArray; // the caller owns this object
Ashok Bhat2bb39d72014-03-05 12:40:53 +0000274 jint fCapacity;
Leon Scrogginscc11f152014-03-31 16:52:13 -0400275 size_t fBytesWritten;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800276};
277
278SkWStream* CreateJavaOutputStreamAdaptor(JNIEnv* env, jobject stream,
279 jbyteArray storage) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800280 return new SkJavaOutputStream(env, stream, storage);
281}
Leon Scroggins IIId0d7eaf2013-09-06 16:46:57 -0400282
283static jclass findClassCheck(JNIEnv* env, const char classname[]) {
284 jclass clazz = env->FindClass(classname);
285 SkASSERT(!env->ExceptionCheck());
286 return clazz;
287}
288
Leon Scroggins IIId0d7eaf2013-09-06 16:46:57 -0400289static jmethodID getMethodIDCheck(JNIEnv* env, jclass clazz,
290 const char methodname[], const char type[]) {
291 jmethodID id = env->GetMethodID(clazz, methodname, type);
292 SkASSERT(!env->ExceptionCheck());
293 return id;
294}
295
296int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env) {
297 jclass inputStream_Clazz = findClassCheck(env, "java/io/InputStream");
Leon Scroggins IIId0d7eaf2013-09-06 16:46:57 -0400298 gInputStream_readMethodID = getMethodIDCheck(env, inputStream_Clazz, "read", "([BII)I");
299 gInputStream_skipMethodID = getMethodIDCheck(env, inputStream_Clazz, "skip", "(J)J");
300
Leon Scroggins IIId0d7eaf2013-09-06 16:46:57 -0400301 jclass outputStream_Clazz = findClassCheck(env, "java/io/OutputStream");
302 gOutputStream_writeMethodID = getMethodIDCheck(env, outputStream_Clazz, "write", "([BII)V");
303 gOutputStream_flushMethodID = getMethodIDCheck(env, outputStream_Clazz, "flush", "()V");
304
305 return 0;
306}