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 {
JavaInputStreamAdaptor(JavaVM * jvm,jobject js,jbyteArray ar,jint capacity,bool swallowExceptions)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:
Create(JNIEnv * env,jobject js,jbyteArray ar,bool swallowExceptions)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 
~JavaInputStreamAdaptor()50     ~JavaInputStreamAdaptor() override {
51         auto* env = android::get_env_or_die(fJvm);
52         env->DeleteGlobalRef(fJavaInputStream);
53         env->DeleteGlobalRef(fJavaByteArray);
54     }
55 
read(void * buffer,size_t size)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 
isAtEnd() const90     bool isAtEnd() const override { return fIsAtEnd; }
91 
92 private:
doRead(void * buffer,size_t size,JNIEnv * env)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 
doSkip(size_t size,JNIEnv * env)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 
checkException(JNIEnv * env)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 
CreateJavaInputStreamAdaptor(JNIEnv * env,jobject stream,jbyteArray storage,bool swallowExceptions)174 SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream, jbyteArray storage,
175                                        bool swallowExceptions) {
176     return JavaInputStreamAdaptor::Create(env, stream, storage, swallowExceptions);
177 }
178 
adaptor_to_mem_stream(SkStream * stream)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 
CopyJavaInputStream(JNIEnv * env,jobject stream,jbyteArray storage)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:
SkJavaOutputStream(JNIEnv * env,jobject stream,jbyteArray storage)217     SkJavaOutputStream(JNIEnv* env, jobject stream, jbyteArray storage)
218         : fEnv(env), fJavaOutputStream(stream), fJavaByteArray(storage), fBytesWritten(0) {
219         fCapacity = env->GetArrayLength(storage);
220     }
221 
bytesWritten() const222     virtual size_t bytesWritten() const {
223         return fBytesWritten;
224     }
225 
write(const void * buffer,size_t size)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 
flush()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 
CreateJavaOutputStreamAdaptor(JNIEnv * env,jobject stream,jbyteArray storage)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 
findClassCheck(JNIEnv * env,const char classname[])289 static jclass findClassCheck(JNIEnv* env, const char classname[]) {
290     jclass clazz = env->FindClass(classname);
291     SkASSERT(!env->ExceptionCheck());
292     return clazz;
293 }
294 
getMethodIDCheck(JNIEnv * env,jclass clazz,const char methodname[],const char type[])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 
register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv * env)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