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
3939 #ifdef __ANDROID__
4040 //-----------------------------------------------------------------------------
4141 #include <jni.h>
42−#include <cassert>
43−#include <memory>
44−#include <functional>
42+#include <fcntl.h>
43+#include <unistd.h>
44+#include <string>
4545
4646 static JavaVM * jvm_instance = NULL;
4747
@@ -57,90 +57,405 @@ JavaVM * ChuckAndroid::getJVM() · 9 unmodified lines
5757 return jvm_instance;
5858 }
5959
60−FILE * 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+//-----------------------------------------------------------------------------
67+class JNIEnvWrapper
6168 {
62− JavaVM * jvm = getJVM();
63− if( !jvm )
64− {
65− return NULL;
66− }
67− JNIEnv * env = NULL;
68− bool needs_detaching = false;
69− int reason = jvm->GetEnv((void **)&env, JNI_VERSION_1_6);
69+public:
70+ JNIEnvWrapper(JavaVM * jvm);
71+ ~JNIEnvWrapper();
72+
73+ bool IsValid() const;
74+
75+ jclass FindClass(const char * class_signature);
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+
104+private:
105+ void ThrowOnJavaException();
106+ JavaVM * m_jvm = NULL;
107+ JNIEnv * m_env = NULL;
108+ bool m_needs_detaching = false;
109+};
110+
111+class JNIEnvWrapperException {};
112+
113+JNIEnvWrapper::JNIEnvWrapper(JavaVM * jvm)
114+ : m_jvm(jvm)
115+{
116+ int reason = jvm->GetEnv((void **)&m_env, JNI_VERSION_1_6);
70117 if( reason != JNI_OK && reason != JNI_EDETACHED )
71118 {
72− return NULL;
119+ m_env = NULL;
120+ return;
73121 }
74122 if( reason == JNI_EDETACHED )
75123 {
76− if( jvm->AttachCurrentThread(&env, NULL) != 0 )
124+ if( jvm->AttachCurrentThread(&m_env, NULL) != 0 )
77125 {
78− return NULL;
126+ m_env = NULL;
127+ return;
79128 }
80− needs_detaching = true;
129+ m_needs_detaching = true;
81130 }
82−
83− std::unique_ptr<JavaVM, std::function<void(JavaVM *)>> guard(jvm,
84− [needs_detaching](JavaVM *jvm)
131+ if( m_env->PushLocalFrame(32) != 0 )
132+ {
133+ if( m_needs_detaching )
85134 {
86− if (needs_detaching)
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; \
135+ m_jvm->DetachCurrentThread();
99136 }
137+ m_env = NULL;
138+ }
139+}
140+
141+JNIEnvWrapper::~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+}
100153
154+bool JNIEnvWrapper::IsValid() const
155+{
156+ return m_env != NULL;
157+}
158+
159+void JNIEnvWrapper::ThrowOnJavaException()
160+{
161+ if( m_env->ExceptionCheck() )
162+ {
163+ m_env->ExceptionDescribe();
164+ m_env->ExceptionClear();
165+ throw JNIEnvWrapperException();
166+ }
167+}
168+
169+jclass JNIEnvWrapper::FindClass(const char * class_signature)
170+{
171+ jclass ret = m_env->FindClass(class_signature);
172+ ThrowOnJavaException();
173+ return ret;
174+}
175+
176+jobject 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+
188+jobject 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+
199+jobject 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+
211+jobject 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+
223+jobject 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+
236+jobject 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+
250+jint 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+
261+void 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+
271+jstring JNIEnvWrapper::NewStringUTF(const char * utf8_string)
272+{
273+ jstring ret = m_env->NewStringUTF(utf8_string);
274+ ThrowOnJavaException();
275+
276+ return ret;
277+}
278+
279+const char * JNIEnvWrapper::GetStringUTFChars(jstring string)
280+{
281+ return m_env->GetStringUTFChars(string, NULL);
282+}
283+
284+void JNIEnvWrapper::ReleaseStringUTFChars(jstring string, const char * utf8_chars)
285+{
286+ m_env->ReleaseStringUTFChars(string, utf8_chars);
287+}
288+
289+jbyteArray JNIEnvWrapper::NewByteArray(size_t size)
290+{
291+ jbyteArray ret = m_env->NewByteArray(size);
292+ ThrowOnJavaException();
293+
294+ return ret;
295+}
296+
297+jbyte * JNIEnvWrapper::GetByteArrayElements(jbyteArray java_array)
298+{
299+ return m_env->GetByteArrayElements(java_array, NULL);
300+}
301+
302+void JNIEnvWrapper::ReleaseByteArrayElements(jbyteArray java_array,
303+ jbyte * native_array)
304+{
305+ m_env->ReleaseByteArrayElements(java_array, native_array, 0);
306+}
307+
308+static std::string GetTemporaryFilePath(JNIEnvWrapper& jni)
309+{
101310 // context = android.app.ActivityThread.currentActivityThread().getApplication()
102− jclass thread_class = env->FindClass("android/app/ActivityThread");
103− EXCEPTION_CHECK;
104− jmethodID get_thread = env->GetStaticMethodID(
105− thread_class, "currentActivityThread", "()Landroid/app/ActivityThread;");
106− EXCEPTION_CHECK;
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;
311+ jclass thread_class = jni.FindClass("android/app/ActivityThread");
312+ jobject current_thread = jni.CallStaticObjectMethod(thread_class,
313+ "currentActivityThread", "()Landroid/app/ActivityThread;");
314+ jobject context = jni.CallObjectMethod(current_thread, thread_class,
315+ "getApplication", "()Landroid/app/Application;");
114316
115317 // cache_dir = context.getCacheDir()
116− jclass context_class = env->FindClass("android/content/Context");
117− EXCEPTION_CHECK;
118− jmethodID get_cache_dir = env->GetMethodID(context_class, "getCacheDir", "()Ljava/io/File;");
119− EXCEPTION_CHECK;
120− jobject cache_dir = env->CallObjectMethod(context, get_cache_dir);
121− EXCEPTION_CHECK;
122−
123− // temp_file = cache_dir.createTempFile("chuck_temp", "", cache_dir)
124− jclass file_class = env->FindClass("java/io/File");
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;
318+ jclass context_class = jni.FindClass("android/content/Context");
319+ jobject cache_dir = jni.CallObjectMethod(context, context_class,
320+ "getCacheDir", "()Ljava/io/File;");
321+
322+ // temp_file = java.io.File.createTempFile("chuck_temp", "", cache_dir)
323+ jclass file_class = jni.FindClass("java/io/File");
324+ jobject temp_file = jni.CallStaticObjectMethod(file_class, "createTempFile",
325+ "(Ljava/lang/String;Ljava/lang/String;Ljava/io/File;)Ljava/io/File;",
326+ jni.NewStringUTF("chuck_temp"), jni.NewStringUTF(""), cache_dir);
132327
133328 // temp_file_path = temp_file.getPath()
134− jmethodID get_path = env->GetMethodID(file_class, "getPath", "()Ljava/lang/String;");
135− EXCEPTION_CHECK;
136− jstring temp_file_path = (jstring)env->CallObjectMethod(temp_file, get_path);
137− EXCEPTION_CHECK;
329+ jstring temp_file_path = (jstring)jni.CallObjectMethod(temp_file,
330+ file_class, "getPath", "()Ljava/lang/String;");
138331
139− const char *path_chars = env->GetStringUTFChars(temp_file_path, NULL);
140− FILE *ret = fopen(path_chars, "wb+D");
141− env->ReleaseStringUTFChars(temp_file_path, path_chars);
332+ const char * path_chars = jni.GetStringUTFChars(temp_file_path);
333+ std::string ret = path_chars;
334+ jni.ReleaseStringUTFChars(temp_file_path, path_chars);
142335
143336 return ret;
144337 }
145338
339+FILE * 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+
363+bool 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+
146461 #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