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