• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #include "CreateJavaOutputStreamAdaptor.h"
2 #include "SkData.h"
3 #include "SkRefCnt.h"
4 #include "SkStream.h"
5 #include "SkTypes.h"
6 #include "Utils.h"
7 
8 #include <cstdlib>
9 #include <nativehelper/JNIHelp.h>
10 #include <log/log.h>
11 #include <memory>
12 
13 static jmethodID    gInputStream_readMethodID;
14 static jmethodID    gInputStream_skipMethodID;
15 
16 /**
17  *  Wrapper for a Java InputStream.
18  */
19 class JavaInputStreamAdaptor : public SkStream {
JavaInputStreamAdaptor(JavaVM * jvm,jobject js,jbyteArray ar,jint capacity,bool swallowExceptions)20     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 
30 public:
Create(JNIEnv * env,jobject js,jbyteArray ar,bool swallowExceptions)31     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);
49     }
50 
~JavaInputStreamAdaptor()51     ~JavaInputStreamAdaptor() override {
52         auto* env = android::requireEnv(fJvm);
53         env->DeleteGlobalRef(fJavaInputStream);
54         env->DeleteGlobalRef(fJavaByteArray);
55     }
56 
read(void * buffer,size_t size)57     size_t read(void* buffer, size_t size) override {
58         auto* env = android::requireEnv(fJvm);
59         if (!fSwallowExceptions && checkException(env)) {
60             // Just in case the caller did not clear from a previous exception.
61             return 0;
62         }
63         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 {
73                     size_t amount = this->doSkip(size - amountSkipped, env);
74                     if (0 == amount) {
75                         char tmp;
76                         amount = this->doRead(&tmp, 1, env);
77                         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         }
88         return this->doRead(buffer, size, env);
89     }
90 
isAtEnd() const91     bool isAtEnd() const override { return fIsAtEnd; }
92 
93 private:
doRead(void * buffer,size_t size,JNIEnv * env)94     size_t doRead(void* buffer, size_t size, JNIEnv* env) {
95         size_t bytesRead = 0;
96         // read the bytes
97         do {
98             jint requested = 0;
99             if (size > static_cast<size_t>(fCapacity)) {
100                 requested = fCapacity;
101             } else {
102                 // This is safe because requested is clamped to (jint)
103                 // fCapacity.
104                 requested = static_cast<jint>(size);
105             }
106 
107             jint n = env->CallIntMethod(fJavaInputStream,
108                                         gInputStream_readMethodID, fJavaByteArray, 0, requested);
109             if (checkException(env)) {
110                 ALOGD("---- read threw an exception\n");
111                 return bytesRead;
112             }
113 
114             if (n < 0) { // n == 0 should not be possible, see InputStream read() specifications.
115                 fIsAtEnd = true;
116                 break;  // eof
117             }
118 
119             env->GetByteArrayRegion(fJavaByteArray, 0, n,
120                                     reinterpret_cast<jbyte*>(buffer));
121             if (checkException(env)) {
122                 ALOGD("---- read:GetByteArrayRegion threw an exception\n");
123                 return bytesRead;
124             }
125 
126             buffer = (void*)((char*)buffer + n);
127             bytesRead += n;
128             size -= n;
129             fBytesRead += n;
130         } while (size != 0);
131 
132         return bytesRead;
133     }
134 
doSkip(size_t size,JNIEnv * env)135     size_t doSkip(size_t size, JNIEnv* env) {
136         jlong skipped = env->CallLongMethod(fJavaInputStream,
137                                             gInputStream_skipMethodID, (jlong)size);
138         if (checkException(env)) {
139             ALOGD("------- skip threw an exception\n");
140             return 0;
141         }
142         if (skipped < 0) {
143             skipped = 0;
144         }
145 
146         return (size_t)skipped;
147     }
148 
checkException(JNIEnv * env)149     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;
170     size_t      fBytesRead;
171     bool        fIsAtEnd;
172     const bool  fSwallowExceptions;
173 };
174 
CreateJavaInputStreamAdaptor(JNIEnv * env,jobject stream,jbyteArray storage,bool swallowExceptions)175 SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream, jbyteArray storage,
176                                        bool swallowExceptions) {
177     return JavaInputStreamAdaptor::Create(env, stream, storage, swallowExceptions);
178 }
179 
free_pointer_skproc(const void * ptr,void *)180 static void free_pointer_skproc(const void* ptr, void*) {
181     free((void*)ptr);
182 }
183 
CopyJavaInputStream(JNIEnv * env,jobject inputStream,jbyteArray storage)184 sk_sp<SkData> CopyJavaInputStream(JNIEnv* env, jobject inputStream, jbyteArray storage) {
185     std::unique_ptr<SkStream> stream(CreateJavaInputStreamAdaptor(env, inputStream, storage));
186     if (!stream) {
187         return nullptr;
188     }
189 
190     size_t bufferSize = 4096;
191     size_t streamLen = 0;
192     size_t len;
193     char* data = (char*)malloc(bufferSize);
194     LOG_ALWAYS_FATAL_IF(!data);
195 
196     while ((len = stream->read(data + streamLen,
197                                bufferSize - streamLen)) != 0) {
198         streamLen += len;
199         if (streamLen == bufferSize) {
200             bufferSize *= 2;
201             data = (char*)realloc(data, bufferSize);
202             LOG_ALWAYS_FATAL_IF(!data);
203         }
204     }
205     if (streamLen == 0) {
206         // realloc with size 0 is unspecified behavior in C++11
207         free(data);
208         data = nullptr;
209     } else {
210         // Trim down the buffer to the actual size of the data.
211         LOG_FATAL_IF(streamLen > bufferSize);
212         data = (char*)realloc(data, streamLen);
213         LOG_ALWAYS_FATAL_IF(!data);
214     }
215     // Just in case sk_free differs from free, we ask Skia to use
216     // free to cleanup the buffer that SkData wraps.
217     return SkData::MakeWithProc(data, streamLen, free_pointer_skproc, nullptr);
218 }
219 
220 ///////////////////////////////////////////////////////////////////////////////
221 
222 static jmethodID    gOutputStream_writeMethodID;
223 static jmethodID    gOutputStream_flushMethodID;
224 
225 class SkJavaOutputStream : public SkWStream {
226 public:
SkJavaOutputStream(JNIEnv * env,jobject stream,jbyteArray storage)227     SkJavaOutputStream(JNIEnv* env, jobject stream, jbyteArray storage)
228         : fEnv(env), fJavaOutputStream(stream), fJavaByteArray(storage), fBytesWritten(0) {
229         fCapacity = env->GetArrayLength(storage);
230     }
231 
bytesWritten() const232     virtual size_t bytesWritten() const {
233         return fBytesWritten;
234     }
235 
write(const void * buffer,size_t size)236     virtual bool write(const void* buffer, size_t size) {
237         JNIEnv* env = fEnv;
238         jbyteArray storage = fJavaByteArray;
239 
240         while (size > 0) {
241             jint requested = 0;
242             if (size > static_cast<size_t>(fCapacity)) {
243                 requested = fCapacity;
244             } else {
245                 // This is safe because requested is clamped to (jint)
246                 // fCapacity.
247                 requested = static_cast<jint>(size);
248             }
249 
250             env->SetByteArrayRegion(storage, 0, requested,
251                                     reinterpret_cast<const jbyte*>(buffer));
252             if (env->ExceptionCheck()) {
253                 env->ExceptionDescribe();
254                 env->ExceptionClear();
255                 ALOGD("--- write:SetByteArrayElements threw an exception\n");
256                 return false;
257             }
258 
259             fEnv->CallVoidMethod(fJavaOutputStream, gOutputStream_writeMethodID,
260                                  storage, 0, requested);
261             if (env->ExceptionCheck()) {
262                 env->ExceptionDescribe();
263                 env->ExceptionClear();
264                 ALOGD("------- write threw an exception\n");
265                 return false;
266             }
267 
268             buffer = (void*)((char*)buffer + requested);
269             size -= requested;
270             fBytesWritten += requested;
271         }
272         return true;
273     }
274 
flush()275     virtual void flush() {
276         fEnv->CallVoidMethod(fJavaOutputStream, gOutputStream_flushMethodID);
277     }
278 
279 private:
280     JNIEnv*     fEnv;
281     jobject     fJavaOutputStream;  // the caller owns this object
282     jbyteArray  fJavaByteArray;     // the caller owns this object
283     jint        fCapacity;
284     size_t      fBytesWritten;
285 };
286 
CreateJavaOutputStreamAdaptor(JNIEnv * env,jobject stream,jbyteArray storage)287 SkWStream* CreateJavaOutputStreamAdaptor(JNIEnv* env, jobject stream,
288                                          jbyteArray storage) {
289     return new SkJavaOutputStream(env, stream, storage);
290 }
291 
findClassCheck(JNIEnv * env,const char classname[])292 static jclass findClassCheck(JNIEnv* env, const char classname[]) {
293     jclass clazz = env->FindClass(classname);
294     SkASSERT(!env->ExceptionCheck());
295     return clazz;
296 }
297 
getMethodIDCheck(JNIEnv * env,jclass clazz,const char methodname[],const char type[])298 static jmethodID getMethodIDCheck(JNIEnv* env, jclass clazz,
299                                   const char methodname[], const char type[]) {
300     jmethodID id = env->GetMethodID(clazz, methodname, type);
301     SkASSERT(!env->ExceptionCheck());
302     return id;
303 }
304 
register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv * env)305 int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env) {
306     jclass inputStream_Clazz = findClassCheck(env, "java/io/InputStream");
307     gInputStream_readMethodID = getMethodIDCheck(env, inputStream_Clazz, "read", "([BII)I");
308     gInputStream_skipMethodID = getMethodIDCheck(env, inputStream_Clazz, "skip", "(J)J");
309 
310     jclass outputStream_Clazz = findClassCheck(env, "java/io/OutputStream");
311     gOutputStream_writeMethodID = getMethodIDCheck(env, outputStream_Clazz, "write", "([BII)V");
312     gOutputStream_flushMethodID = getMethodIDCheck(env, outputStream_Clazz, "flush", "()V");
313 
314     return 0;
315 }
316