History

Add loading audio files from JARs on Android

Andriy Kunitsyn committed 5 years ago

Commit bca7db61 parent 9d73a413 files · +421 −79Browse files

Showing the changes to src/core/util_platforms.cpp (1 of 3 files). Show all

Unified · Split

src/core/util_platforms.cpp+382−67
@@ -39,9 +39,9 @@ · 38 unmodified lines
39#ifdef __ANDROID__39#ifdef __ANDROID__
40//-----------------------------------------------------------------------------40//-----------------------------------------------------------------------------
41#include <jni.h>41#include <jni.h>
42#include <cassert>42#include <fcntl.h>
43#include <memory>43#include <unistd.h>
44#include <functional>44#include <string>
45 45
46static JavaVM * jvm_instance = NULL;46static JavaVM * jvm_instance = NULL;
47 47
@@ -57,90 +57,405 @@ JavaVM * ChuckAndroid::getJVM() · 9 unmodified lines
57 return jvm_instance;57 return jvm_instance;
58}58}
59 59
60FILE * ChuckAndroid::getTemporaryFile()60//-----------------------------------------------------------------------------
61// name: class JNIEnvWrapper
62// desc: Wrapper around JNIEnv. Serves three purposes:
63// - releases JNIEnv and Java objects in RAII fashion
64// - removes the need to get method IDs
65// - throws JNIEnvWrapperException exception on Java exceptions
66//-----------------------------------------------------------------------------
67class JNIEnvWrapper
61{68{
62 JavaVM * jvm = getJVM();69public:
63 if( !jvm )70 JNIEnvWrapper(JavaVM * jvm);
64 {71 ~JNIEnvWrapper();
65 return NULL;72
66 }73 bool IsValid() const;
67 JNIEnv * env = NULL;74
68 bool needs_detaching = false;75 jclass FindClass(const char * class_signature);
69 int reason = jvm->GetEnv((void **)&env, JNI_VERSION_1_6);76 jobject ConstructNewObject(jclass constructed_class,
77 const char * constructor_signature, jobject param_1);
78 jobject CallObjectMethod(jobject callee, jclass callee_class,
79 const char * method_name, const char * method_signature);
80 jobject CallObjectMethod(jobject callee, jclass callee_class,
81 const char * method_name, const char * method_signature,
82 jobject param_1);
83 jobject CallStaticObjectMethod(jclass callee_class,
84 const char * method_name, const char * method_signature);
85 jobject CallStaticObjectMethod(jclass callee_class,
86 const char * method_name, const char * method_signature,
87 jobject param_1);
88 jobject CallStaticObjectMethod(jclass callee_class,
89 const char * method_name, const char * method_signature,
90 jobject param_1, jobject param_2, jobject param_3);
91 jint CallIntMethod(jobject callee, jclass callee_class,
92 const char * method_name, const char * method_signature,
93 jobject param_1);
94 void CallVoidMethod(jobject callee, jclass callee_class,
95 const char * method_name, const char * method_signature);
96
97 jstring NewStringUTF(const char * utf8_string);
98 const char * GetStringUTFChars(jstring string);
99 void ReleaseStringUTFChars(jstring string, const char * utf8_chars);
100 jbyteArray NewByteArray(size_t size);
101 jbyte * GetByteArrayElements(jbyteArray java_array);
102 void ReleaseByteArrayElements(jbyteArray java_array, jbyte * native_array);
103
104private:
105 void ThrowOnJavaException();
106 JavaVM * m_jvm = NULL;
107 JNIEnv * m_env = NULL;
108 bool m_needs_detaching = false;
109};
110
111class JNIEnvWrapperException {};
112
113JNIEnvWrapper::JNIEnvWrapper(JavaVM * jvm)
114 : m_jvm(jvm)
115{
116 int reason = jvm->GetEnv((void **)&m_env, JNI_VERSION_1_6);
70 if( reason != JNI_OK && reason != JNI_EDETACHED )117 if( reason != JNI_OK && reason != JNI_EDETACHED )
71 {118 {
72 return NULL;119 m_env = NULL;
120 return;
73 }121 }
74 if( reason == JNI_EDETACHED )122 if( reason == JNI_EDETACHED )
75 {123 {
76 if( jvm->AttachCurrentThread(&env, NULL) != 0 )124 if( jvm->AttachCurrentThread(&m_env, NULL) != 0 )
77 {125 {
78 return NULL;126 m_env = NULL;
127 return;
79 }128 }
80 needs_detaching = true;129 m_needs_detaching = true;
81 }130 }
82 131 if( m_env->PushLocalFrame(32) != 0 )
83 std::unique_ptr<JavaVM, std::function<void(JavaVM *)>> guard(jvm,132 {
84 [needs_detaching](JavaVM *jvm)133 if( m_needs_detaching )
85 {134 {
86 if (needs_detaching)135 m_jvm->DetachCurrentThread();
87 {
88 jvm->DetachCurrentThread();
89 }
90 });
91
92 assert( env != NULL );
93 #define EXCEPTION_CHECK \
94 if (env->ExceptionCheck()) \
95 { \
96 env->ExceptionDescribe(); \
97 env->ExceptionClear(); \
98 return NULL; \
99 }136 }
137 m_env = NULL;
138 }
139}
140
141JNIEnvWrapper::~JNIEnvWrapper()
142{
143 if( m_env == NULL )
144 {
145 return;
146 }
147 m_env->PopLocalFrame(NULL);
148 if( m_needs_detaching )
149 {
150 m_jvm->DetachCurrentThread();
151 }
152}
100 153
154bool JNIEnvWrapper::IsValid() const
155{
156 return m_env != NULL;
157}
158
159void JNIEnvWrapper::ThrowOnJavaException()
160{
161 if( m_env->ExceptionCheck() )
162 {
163 m_env->ExceptionDescribe();
164 m_env->ExceptionClear();
165 throw JNIEnvWrapperException();
166 }
167}
168
169jclass JNIEnvWrapper::FindClass(const char * class_signature)
170{
171 jclass ret = m_env->FindClass(class_signature);
172 ThrowOnJavaException();
173 return ret;
174}
175
176jobject JNIEnvWrapper::ConstructNewObject(jclass constructed_class,
177 const char * constructor_signature, jobject param_1)
178{
179 jmethodID method_id = m_env->GetMethodID(constructed_class,
180 "<init>", constructor_signature);
181 ThrowOnJavaException();
182 jobject ret = m_env->NewObject(constructed_class, method_id,
183 param_1);
184 ThrowOnJavaException();
185 return ret;
186}
187
188jobject JNIEnvWrapper::CallObjectMethod(jobject callee, jclass callee_class,
189 const char * method_name, const char * method_signature)
190{
191 jmethodID method_id = m_env->GetMethodID(callee_class,
192 method_name, method_signature);
193 ThrowOnJavaException();
194 jobject ret = m_env->CallObjectMethod(callee, method_id);
195 ThrowOnJavaException();
196 return ret;
197}
198
199jobject JNIEnvWrapper::CallObjectMethod(jobject callee, jclass callee_class,
200 const char * method_name, const char * method_signature,
201 jobject param_1)
202{
203 jmethodID method_id = m_env->GetMethodID(callee_class,
204 method_name, method_signature);
205 ThrowOnJavaException();
206 jobject ret = m_env->CallObjectMethod(callee, method_id, param_1);
207 ThrowOnJavaException();
208 return ret;
209}
210
211jobject JNIEnvWrapper::CallStaticObjectMethod(jclass callee_class,
212 const char * method_name, const char * method_signature)
213{
214 jmethodID method_id = m_env->GetStaticMethodID(callee_class,
215 method_name, method_signature);
216 ThrowOnJavaException();
217 jobject ret = m_env->CallStaticObjectMethod(callee_class, method_id);
218 ThrowOnJavaException();
219
220 return ret;
221}
222
223jobject JNIEnvWrapper::CallStaticObjectMethod(jclass callee_class,
224 const char * method_name, const char * method_signature, jobject param_1)
225{
226 jmethodID method_id = m_env->GetStaticMethodID(callee_class,
227 method_name, method_signature);
228 ThrowOnJavaException();
229 jobject ret = m_env->CallStaticObjectMethod(callee_class,
230 method_id, param_1);
231 ThrowOnJavaException();
232
233 return ret;
234}
235
236jobject JNIEnvWrapper::CallStaticObjectMethod(jclass callee_class,
237 const char * method_name, const char * method_signature, jobject param_1,
238 jobject param_2, jobject param_3)
239{
240 jmethodID method_id = m_env->GetStaticMethodID(callee_class,
241 method_name, method_signature);
242 ThrowOnJavaException();
243 jobject ret = m_env->CallStaticObjectMethod(callee_class, method_id,
244 param_1, param_2, param_3);
245 ThrowOnJavaException();
246
247 return ret;
248}
249
250jint JNIEnvWrapper::CallIntMethod(jobject callee, jclass callee_class,
251 const char * method_name, const char * method_signature, jobject param_1)
252{
253 jmethodID method_id = m_env->GetMethodID(callee_class,
254 method_name, method_signature);
255 ThrowOnJavaException();
256 jint ret = m_env->CallIntMethod(callee, method_id, param_1);
257 ThrowOnJavaException();
258 return ret;
259}
260
261void JNIEnvWrapper::CallVoidMethod(jobject callee, jclass callee_class,
262 const char * method_name, const char * method_signature)
263{
264 jmethodID method_id = m_env->GetMethodID(callee_class,
265 method_name, method_signature);
266 ThrowOnJavaException();
267 m_env->CallVoidMethod(callee, method_id);
268 ThrowOnJavaException();
269}
270
271jstring JNIEnvWrapper::NewStringUTF(const char * utf8_string)
272{
273 jstring ret = m_env->NewStringUTF(utf8_string);
274 ThrowOnJavaException();
275
276 return ret;
277}
278
279const char * JNIEnvWrapper::GetStringUTFChars(jstring string)
280{
281 return m_env->GetStringUTFChars(string, NULL);
282}
283
284void JNIEnvWrapper::ReleaseStringUTFChars(jstring string, const char * utf8_chars)
285{
286 m_env->ReleaseStringUTFChars(string, utf8_chars);
287}
288
289jbyteArray JNIEnvWrapper::NewByteArray(size_t size)
290{
291 jbyteArray ret = m_env->NewByteArray(size);
292 ThrowOnJavaException();
293
294 return ret;
295}
296
297jbyte * JNIEnvWrapper::GetByteArrayElements(jbyteArray java_array)
298{
299 return m_env->GetByteArrayElements(java_array, NULL);
300}
301
302void JNIEnvWrapper::ReleaseByteArrayElements(jbyteArray java_array,
303 jbyte * native_array)
304{
305 m_env->ReleaseByteArrayElements(java_array, native_array, 0);
306}
307
308static std::string GetTemporaryFilePath(JNIEnvWrapper& jni)
309{
101 // context = android.app.ActivityThread.currentActivityThread().getApplication()310 // context = android.app.ActivityThread.currentActivityThread().getApplication()
102 jclass thread_class = env->FindClass("android/app/ActivityThread");311 jclass thread_class = jni.FindClass("android/app/ActivityThread");
103 EXCEPTION_CHECK;312 jobject current_thread = jni.CallStaticObjectMethod(thread_class,
104 jmethodID get_thread = env->GetStaticMethodID(313 "currentActivityThread", "()Landroid/app/ActivityThread;");
105 thread_class, "currentActivityThread", "()Landroid/app/ActivityThread;");314 jobject context = jni.CallObjectMethod(current_thread, thread_class,
106 EXCEPTION_CHECK;315 "getApplication", "()Landroid/app/Application;");
107 jobject current_thread = env->CallStaticObjectMethod(thread_class, get_thread);
108 EXCEPTION_CHECK;
109 jmethodID get_application = env->GetMethodID(
110 thread_class, "getApplication", "()Landroid/app/Application;");
111 EXCEPTION_CHECK;
112 jobject context = env->CallObjectMethod(current_thread, get_application);
113 EXCEPTION_CHECK;
114 316
115 // cache_dir = context.getCacheDir()317 // cache_dir = context.getCacheDir()
116 jclass context_class = env->FindClass("android/content/Context");318 jclass context_class = jni.FindClass("android/content/Context");
117 EXCEPTION_CHECK;319 jobject cache_dir = jni.CallObjectMethod(context, context_class,
118 jmethodID get_cache_dir = env->GetMethodID(context_class, "getCacheDir", "()Ljava/io/File;");320 "getCacheDir", "()Ljava/io/File;");
119 EXCEPTION_CHECK;321
120 jobject cache_dir = env->CallObjectMethod(context, get_cache_dir);322 // temp_file = java.io.File.createTempFile("chuck_temp", "", cache_dir)
121 EXCEPTION_CHECK;323 jclass file_class = jni.FindClass("java/io/File");
122 324 jobject temp_file = jni.CallStaticObjectMethod(file_class, "createTempFile",
123 // temp_file = cache_dir.createTempFile("chuck_temp", "", cache_dir)325 "(Ljava/lang/String;Ljava/lang/String;Ljava/io/File;)Ljava/io/File;",
124 jclass file_class = env->FindClass("java/io/File");326 jni.NewStringUTF("chuck_temp"), jni.NewStringUTF(""), cache_dir);
125 EXCEPTION_CHECK;
126 jmethodID create_temp_file = env->GetStaticMethodID(file_class, "createTempFile",
127 "(Ljava/lang/String;Ljava/lang/String;Ljava/io/File;)Ljava/io/File;");
128 EXCEPTION_CHECK;
129 jobject temp_file = env->CallStaticObjectMethod(file_class, create_temp_file,
130 env->NewStringUTF("chuck_temp"), env->NewStringUTF(""), cache_dir);
131 EXCEPTION_CHECK;
132 327
133 // temp_file_path = temp_file.getPath()328 // temp_file_path = temp_file.getPath()
134 jmethodID get_path = env->GetMethodID(file_class, "getPath", "()Ljava/lang/String;");329 jstring temp_file_path = (jstring)jni.CallObjectMethod(temp_file,
135 EXCEPTION_CHECK;330 file_class, "getPath", "()Ljava/lang/String;");
136 jstring temp_file_path = (jstring)env->CallObjectMethod(temp_file, get_path);
137 EXCEPTION_CHECK;
138 331
139 const char *path_chars = env->GetStringUTFChars(temp_file_path, NULL);332 const char * path_chars = jni.GetStringUTFChars(temp_file_path);
140 FILE *ret = fopen(path_chars, "wb+D");333 std::string ret = path_chars;
141 env->ReleaseStringUTFChars(temp_file_path, path_chars);334 jni.ReleaseStringUTFChars(temp_file_path, path_chars);
142 335
143 return ret;336 return ret;
144}337}
145 338
339FILE * ChuckAndroid::getTemporaryFile() noexcept
340{
341 JavaVM * jvm = getJVM();
342 if( !jvm )
343 {
344 return NULL;
345 }
346 JNIEnvWrapper jni(jvm);
347 if( !jni.IsValid() )
348 {
349 return NULL;
350 }
351
352 try
353 {
354 const std::string temp_path = GetTemporaryFilePath(jni);
355 return fopen(temp_path.c_str(), "wb+");
356 }
357 catch (JNIEnvWrapperException)
358 {
359 return NULL;
360 }
361}
362
363bool ChuckAndroid::copyJARURLFileToTemporary(const char * jar_url, int * fd)
364 noexcept
365{
366 JavaVM * jvm = getJVM();
367 if( !jvm )
368 {
369 return false;
370 }
371 JNIEnvWrapper jni(jvm);
372 if( !jni.IsValid() )
373 {
374 return false;
375 }
376
377 int ret = 0;
378 try
379 {
380 const std::string temp_path = GetTemporaryFilePath(jni);
381 ret = open(temp_path.c_str(), O_RDWR | O_CREAT, 0600);
382 if( ret == -1 )
383 {
384 return false;
385 }
386 // do not pollute temp directory
387 if( unlink(temp_path.c_str()) == -1 )
388 {
389 return false;
390 }
391
392 // url = new java.net.URL(jar_url)
393 jclass url_class = jni.FindClass("java/net/URL");
394 jobject url = jni.ConstructNewObject(url_class,
395 "(Ljava/lang/String;)V", jni.NewStringUTF(jar_url));
396
397 // connection = url.openConnection()
398 jobject connection = jni.CallObjectMethod(url, url_class,
399 "openConnection", "()Ljava/net/URLConnection;");
400
401 // jar_file = (java.net.JarURLConnection)connection.getJarFile()
402 jclass jar_url_connection_class = jni.FindClass(
403 "java/net/JarURLConnection");
404 jobject jar_file = jni.CallObjectMethod(connection,
405 jar_url_connection_class, "getJarFile",
406 "()Ljava/util/jar/JarFile;");
407
408 // jar_entry = connection.getJarEntry()
409 jobject jar_entry = jni.CallObjectMethod(connection,
410 jar_url_connection_class, "getJarEntry",
411 "()Ljava/util/jar/JarEntry;");
412
413 // input_stream = jar_file.getInputStream(jar_entry)
414 jclass jar_file_class = jni.FindClass("java/util/jar/JarFile");
415 jobject input_stream = jni.CallObjectMethod(jar_file, jar_file_class,
416 "getInputStream", "(Ljava/util/zip/ZipEntry;)Ljava/io/InputStream;",
417 jar_entry);
418
419 // buffer = new byte[8 * 1024]
420 jbyteArray java_buffer = jni.NewByteArray(8 * 1024);
421 // s = input_stream.read(buffer)
422 jclass input_stream_class = jni.FindClass("java/io/InputStream");
423 jint s = 0;
424 do
425 {
426 s = jni.CallIntMethod(input_stream, input_stream_class, "read",
427 "([B)I", java_buffer);
428 jbyte * native_buffer = jni.GetByteArrayElements(java_buffer);
429 size_t written = 0;
430 while( written < s )
431 {
432 ssize_t written_now = write(ret, native_buffer + written,
433 s - written);
434 if( written_now < 0 )
435 {
436 close(ret);
437 return false;
438 }
439 written += written_now;
440 }
441 jni.ReleaseByteArrayElements(java_buffer, native_buffer);
442 } while( s > 0 );
443
444 // jar_file.close()
445 jni.CallVoidMethod(jar_file, jar_file_class, "close", "()V");
446 }
447 catch (JNIEnvWrapperException)
448 {
449 if( ret != -1 )
450 {
451 close(ret);
452 }
453 return false;
454 }
455
456 lseek(ret, 0, SEEK_SET);
457 *fd = ret;
458 return true;
459}
460
146#endif // __ANDROID__461#endif // __ANDROID__

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t