1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #if defined(__ANDROID__)
24
25 // Allow use of stuff in <time.h> and abort()
26 #define FORBIDDEN_SYMBOL_EXCEPTION_time_h
27 #define FORBIDDEN_SYMBOL_EXCEPTION_abort
28
29 // Disable printf override in common/forbidden.h to avoid
30 // clashes with log.h from the Android SDK.
31 // That header file uses
32 // __attribute__ ((format(printf, 3, 4)))
33 // which gets messed up by our override mechanism; this could
34 // be avoided by either changing the Android SDK to use the equally
35 // legal and valid
36 // __attribute__ ((format(printf, 3, 4)))
37 // or by refining our printf override to use a varadic macro
38 // (which then wouldn't be portable, though).
39 // Anyway, for now we just disable the printf override globally
40 // for the Android port
41 #define FORBIDDEN_SYMBOL_EXCEPTION_printf
42
43 #include "base/main.h"
44 #include "base/version.h"
45 #include "common/config-manager.h"
46 #include "common/error.h"
47 #include "common/textconsole.h"
48 #include "engines/engine.h"
49
50 #include "backends/platform/android/android.h"
51 #include "backends/platform/android/asset-archive.h"
52 #include "backends/platform/android/jni-android.h"
53
54 __attribute__ ((visibility("default")))
JNI_OnLoad(JavaVM * vm,void *)55 jint JNICALL JNI_OnLoad(JavaVM *vm, void *) {
56 return JNI::onLoad(vm);
57 }
58
59 JavaVM *JNI::_vm = 0;
60 jobject JNI::_jobj = 0;
61 jobject JNI::_jobj_audio_track = 0;
62 jobject JNI::_jobj_egl = 0;
63 jobject JNI::_jobj_egl_display = 0;
64 jobject JNI::_jobj_egl_surface = 0;
65
66 Common::Archive *JNI::_asset_archive = 0;
67 OSystem_Android *JNI::_system = 0;
68
69 bool JNI::pause = false;
70 sem_t JNI::pause_sem = { 0 };
71
72 int JNI::surface_changeid = 0;
73 int JNI::egl_surface_width = 0;
74 int JNI::egl_surface_height = 0;
75 bool JNI::_ready_for_events = 0;
76
77 jmethodID JNI::_MID_getDPI = 0;
78 jmethodID JNI::_MID_displayMessageOnOSD = 0;
79 jmethodID JNI::_MID_openUrl = 0;
80 jmethodID JNI::_MID_hasTextInClipboard = 0;
81 jmethodID JNI::_MID_getTextFromClipboard = 0;
82 jmethodID JNI::_MID_setTextInClipboard = 0;
83 jmethodID JNI::_MID_isConnectionLimited = 0;
84 jmethodID JNI::_MID_setWindowCaption = 0;
85 jmethodID JNI::_MID_showVirtualKeyboard = 0;
86 jmethodID JNI::_MID_showKeyboardControl = 0;
87 jmethodID JNI::_MID_showSAFRevokePermsControl = 0;
88 jmethodID JNI::_MID_getSysArchives = 0;
89 jmethodID JNI::_MID_getAllStorageLocations = 0;
90 jmethodID JNI::_MID_initSurface = 0;
91 jmethodID JNI::_MID_deinitSurface = 0;
92 jmethodID JNI::_MID_createDirectoryWithSAF = 0;
93 jmethodID JNI::_MID_createFileWithSAF = 0;
94 jmethodID JNI::_MID_closeFileWithSAF = 0;
95 jmethodID JNI::_MID_isDirectoryWritableWithSAF = 0;
96
97 jmethodID JNI::_MID_EGL10_eglSwapBuffers = 0;
98
99 jmethodID JNI::_MID_AudioTrack_flush = 0;
100 jmethodID JNI::_MID_AudioTrack_pause = 0;
101 jmethodID JNI::_MID_AudioTrack_play = 0;
102 jmethodID JNI::_MID_AudioTrack_stop = 0;
103 jmethodID JNI::_MID_AudioTrack_write = 0;
104
105 PauseToken JNI::_pauseToken;
106
107 const JNINativeMethod JNI::_natives[] = {
108 { "create", "(Landroid/content/res/AssetManager;"
109 "Ljavax/microedition/khronos/egl/EGL10;"
110 "Ljavax/microedition/khronos/egl/EGLDisplay;"
111 "Landroid/media/AudioTrack;II)V",
112 (void *)JNI::create },
113 { "destroy", "()V",
114 (void *)JNI::destroy },
115 { "setSurface", "(II)V",
116 (void *)JNI::setSurface },
117 { "main", "([Ljava/lang/String;)I",
118 (void *)JNI::main },
119 { "pushEvent", "(IIIIIII)V",
120 (void *)JNI::pushEvent },
121 { "setPause", "(Z)V",
122 (void *)JNI::setPause },
123 { "getNativeVersionInfo", "()Ljava/lang/String;",
124 (void *)JNI::getNativeVersionInfo }
125 };
126
JNI()127 JNI::JNI() {
128 }
129
~JNI()130 JNI::~JNI() {
131 }
132
onLoad(JavaVM * vm)133 jint JNI::onLoad(JavaVM *vm) {
134 _vm = vm;
135
136 JNIEnv *env;
137
138 if (_vm->GetEnv((void **)&env, JNI_VERSION_1_2))
139 return JNI_ERR;
140
141 #ifdef BACKEND_ANDROID3D
142 jclass cls = env->FindClass("org/residualvm/residualvm/ResidualVM");
143 #else
144 jclass cls = env->FindClass("org/scummvm/scummvm/ScummVM");
145 #endif
146 if (cls == 0)
147 return JNI_ERR;
148
149 if (env->RegisterNatives(cls, _natives, ARRAYSIZE(_natives)) < 0)
150 return JNI_ERR;
151
152 return JNI_VERSION_1_2;
153 }
154
getEnv()155 JNIEnv *JNI::getEnv() {
156 JNIEnv *env = 0;
157
158 jint res = _vm->GetEnv((void **)&env, JNI_VERSION_1_2);
159
160 if (res != JNI_OK) {
161 LOGE("GetEnv() failed: %d", res);
162 abort();
163 }
164
165 return env;
166 }
167
attachThread()168 void JNI::attachThread() {
169 JNIEnv *env = 0;
170
171 jint res = _vm->AttachCurrentThread(&env, 0);
172
173 if (res != JNI_OK) {
174 LOGE("AttachCurrentThread() failed: %d", res);
175 abort();
176 }
177 }
178
detachThread()179 void JNI::detachThread() {
180 jint res = _vm->DetachCurrentThread();
181
182 if (res != JNI_OK) {
183 LOGE("DetachCurrentThread() failed: %d", res);
184 abort();
185 }
186 }
187
setReadyForEvents(bool ready)188 void JNI::setReadyForEvents(bool ready) {
189 _ready_for_events = ready;
190 }
191
throwByName(JNIEnv * env,const char * name,const char * msg)192 void JNI::throwByName(JNIEnv *env, const char *name, const char *msg) {
193 jclass cls = env->FindClass(name);
194
195 // if cls is 0, an exception has already been thrown
196 if (cls != 0)
197 env->ThrowNew(cls, msg);
198
199 env->DeleteLocalRef(cls);
200 }
201
throwRuntimeException(JNIEnv * env,const char * msg)202 void JNI::throwRuntimeException(JNIEnv *env, const char *msg) {
203 throwByName(env, "java/lang/RuntimeException", msg);
204 }
205
206 // calls to the dark side
207
getDPI(float * values)208 void JNI::getDPI(float *values) {
209 values[0] = 0.0;
210 values[1] = 0.0;
211
212 JNIEnv *env = JNI::getEnv();
213
214 jfloatArray array = env->NewFloatArray(2);
215
216 env->CallVoidMethod(_jobj, _MID_getDPI, array);
217
218 if (env->ExceptionCheck()) {
219 LOGE("Failed to get DPIs");
220
221 env->ExceptionDescribe();
222 env->ExceptionClear();
223 } else {
224 jfloat *res = env->GetFloatArrayElements(array, 0);
225
226 if (res) {
227 values[0] = res[0];
228 values[1] = res[1];
229
230 env->ReleaseFloatArrayElements(array, res, 0);
231 }
232 }
233 LOGD("JNI::getDPI() xdpi: %f, ydpi: %f", values[0], values[1]);
234 env->DeleteLocalRef(array);
235 }
236
displayMessageOnOSD(const Common::U32String & msg)237 void JNI::displayMessageOnOSD(const Common::U32String &msg) {
238 // called from common/osd_message_queue, method: OSDMessageQueue::pollEvent()
239 JNIEnv *env = JNI::getEnv();
240
241 jstring java_msg = convertToJString(env, msg);
242 if (java_msg == nullptr) {
243 // Show a placeholder indicative of the translation error instead of silent failing
244 java_msg = env->NewStringUTF("?");
245 LOGE("Failed to convert message to UTF-8 for OSD!");
246 }
247
248 env->CallVoidMethod(_jobj, _MID_displayMessageOnOSD, java_msg);
249
250 if (env->ExceptionCheck()) {
251 LOGE("Failed to display OSD message");
252
253 env->ExceptionDescribe();
254 env->ExceptionClear();
255 }
256
257 env->DeleteLocalRef(java_msg);
258 }
259
openUrl(const Common::String & url)260 bool JNI::openUrl(const Common::String &url) {
261 bool success = true;
262 JNIEnv *env = JNI::getEnv();
263 jstring javaUrl = env->NewStringUTF(url.c_str());
264
265 env->CallVoidMethod(_jobj, _MID_openUrl, javaUrl);
266
267 if (env->ExceptionCheck()) {
268 LOGE("Failed to open URL");
269
270 env->ExceptionDescribe();
271 env->ExceptionClear();
272 success = false;
273 }
274
275 env->DeleteLocalRef(javaUrl);
276 return success;
277 }
278
hasTextInClipboard()279 bool JNI::hasTextInClipboard() {
280 JNIEnv *env = JNI::getEnv();
281 bool hasText = env->CallBooleanMethod(_jobj, _MID_hasTextInClipboard);
282
283 if (env->ExceptionCheck()) {
284 LOGE("Failed to check the contents of the clipboard");
285
286 env->ExceptionDescribe();
287 env->ExceptionClear();
288 hasText = true;
289 }
290
291 return hasText;
292 }
293
getTextFromClipboard()294 Common::U32String JNI::getTextFromClipboard() {
295 JNIEnv *env = JNI::getEnv();
296
297 jstring javaText = (jstring)env->CallObjectMethod(_jobj, _MID_getTextFromClipboard);
298
299 if (env->ExceptionCheck()) {
300 LOGE("Failed to retrieve text from the clipboard");
301
302 env->ExceptionDescribe();
303 env->ExceptionClear();
304
305 return Common::U32String();
306 }
307
308 Common::U32String text = convertFromJString(env, javaText);
309 env->DeleteLocalRef(javaText);
310
311 return text;
312 }
313
setTextInClipboard(const Common::U32String & text)314 bool JNI::setTextInClipboard(const Common::U32String &text) {
315 JNIEnv *env = JNI::getEnv();
316 jstring javaText = convertToJString(env, text);
317
318 bool success = env->CallBooleanMethod(_jobj, _MID_setTextInClipboard, javaText);
319
320 if (env->ExceptionCheck()) {
321 LOGE("Failed to add text to the clipboard");
322
323 env->ExceptionDescribe();
324 env->ExceptionClear();
325 success = false;
326 }
327
328 env->DeleteLocalRef(javaText);
329 return success;
330 }
331
isConnectionLimited()332 bool JNI::isConnectionLimited() {
333 JNIEnv *env = JNI::getEnv();
334 bool limited = env->CallBooleanMethod(_jobj, _MID_isConnectionLimited);
335
336 if (env->ExceptionCheck()) {
337 LOGE("Failed to check whether connection's limited");
338
339 env->ExceptionDescribe();
340 env->ExceptionClear();
341 limited = true;
342 }
343
344 return limited;
345 }
346
setWindowCaption(const Common::U32String & caption)347 void JNI::setWindowCaption(const Common::U32String &caption) {
348 JNIEnv *env = JNI::getEnv();
349 jstring java_caption = convertToJString(env, caption);
350
351 env->CallVoidMethod(_jobj, _MID_setWindowCaption, java_caption);
352
353 if (env->ExceptionCheck()) {
354 LOGE("Failed to set window caption");
355
356 env->ExceptionDescribe();
357 env->ExceptionClear();
358 }
359
360 env->DeleteLocalRef(java_caption);
361 }
362
showVirtualKeyboard(bool enable)363 void JNI::showVirtualKeyboard(bool enable) {
364 JNIEnv *env = JNI::getEnv();
365
366 env->CallVoidMethod(_jobj, _MID_showVirtualKeyboard, enable);
367
368 if (env->ExceptionCheck()) {
369 LOGE("Error trying to show virtual keyboard");
370
371 env->ExceptionDescribe();
372 env->ExceptionClear();
373 }
374 }
375
showKeyboardControl(bool enable)376 void JNI::showKeyboardControl(bool enable) {
377 JNIEnv *env = JNI::getEnv();
378
379 env->CallVoidMethod(_jobj, _MID_showKeyboardControl, enable);
380
381 if (env->ExceptionCheck()) {
382 LOGE("Error trying to show virtual keyboard control");
383
384 env->ExceptionDescribe();
385 env->ExceptionClear();
386 }
387 }
388
showSAFRevokePermsControl(bool enable)389 void JNI::showSAFRevokePermsControl(bool enable) {
390 #ifndef BACKEND_ANDROID3D
391 JNIEnv *env = JNI::getEnv();
392
393 env->CallVoidMethod(_jobj, _MID_showSAFRevokePermsControl, enable);
394
395 if (env->ExceptionCheck()) {
396 LOGE("Error trying to show the revoke SAF permissions button");
397
398 env->ExceptionDescribe();
399 env->ExceptionClear();
400 }
401 #endif
402 }
403
404 // The following adds assets folder to search set.
405 // However searching and retrieving from "assets" on Android this is slow
406 // so we also make sure to add the "path" directory, with a higher priority
407 // This is done via a call to ScummVMActivity's (java) getSysArchives
addSysArchivesToSearchSet(Common::SearchSet & s,int priority)408 void JNI::addSysArchivesToSearchSet(Common::SearchSet &s, int priority) {
409 JNIEnv *env = JNI::getEnv();
410
411 // get any additional specified paths (from ScummVMActivity code)
412 // Insert them with "priority" priority.
413 jobjectArray array =
414 (jobjectArray)env->CallObjectMethod(_jobj, _MID_getSysArchives);
415
416 if (env->ExceptionCheck()) {
417 LOGE("Error finding system archive path");
418
419 env->ExceptionDescribe();
420 env->ExceptionClear();
421
422 return;
423 }
424
425 jsize size = env->GetArrayLength(array);
426 for (jsize i = 0; i < size; ++i) {
427 jstring path_obj = (jstring)env->GetObjectArrayElement(array, i);
428 const char *path = env->GetStringUTFChars(path_obj, 0);
429
430 if (path != 0) {
431 s.addDirectory(path, path, priority);
432 env->ReleaseStringUTFChars(path_obj, path);
433 }
434
435 env->DeleteLocalRef(path_obj);
436 }
437
438 // add the internal asset (android's structure) with a lower priority,
439 // since:
440 // 1. It is very slow in accessing large files (eg our growing fonts.dat)
441 // 2. we extract the asset contents anyway to the internal app path
442 // 3. we pass the internal app path in the process above (via _MID_getSysArchives)
443 // However, we keep android APK's "assets" as a fall back, in case something went wrong with the extraction process
444 // and since we had the code anyway
445 s.add("ASSET", _asset_archive, priority - 1, false);
446 }
447
initSurface()448 bool JNI::initSurface() {
449 JNIEnv *env = JNI::getEnv();
450
451 jobject obj = env->CallObjectMethod(_jobj, _MID_initSurface);
452
453 if (!obj || env->ExceptionCheck()) {
454 LOGE("initSurface failed");
455
456 env->ExceptionDescribe();
457 env->ExceptionClear();
458
459 return false;
460 }
461
462 _jobj_egl_surface = env->NewGlobalRef(obj);
463
464 return true;
465 }
466
deinitSurface()467 void JNI::deinitSurface() {
468 JNIEnv *env = JNI::getEnv();
469
470 env->CallVoidMethod(_jobj, _MID_deinitSurface);
471
472 if (env->ExceptionCheck()) {
473 LOGE("deinitSurface failed");
474
475 env->ExceptionDescribe();
476 env->ExceptionClear();
477 }
478
479 env->DeleteGlobalRef(_jobj_egl_surface);
480 _jobj_egl_surface = 0;
481 }
482
setAudioPause()483 void JNI::setAudioPause() {
484 JNIEnv *env = JNI::getEnv();
485
486 env->CallVoidMethod(_jobj_audio_track, _MID_AudioTrack_flush);
487
488 if (env->ExceptionCheck()) {
489 LOGE("Error flushing AudioTrack");
490
491 env->ExceptionDescribe();
492 env->ExceptionClear();
493 }
494
495 env->CallVoidMethod(_jobj_audio_track, _MID_AudioTrack_pause);
496
497 if (env->ExceptionCheck()) {
498 LOGE("Error setting AudioTrack: pause");
499
500 env->ExceptionDescribe();
501 env->ExceptionClear();
502 }
503 }
504
setAudioPlay()505 void JNI::setAudioPlay() {
506 JNIEnv *env = JNI::getEnv();
507
508 env->CallVoidMethod(_jobj_audio_track, _MID_AudioTrack_play);
509
510 if (env->ExceptionCheck()) {
511 LOGE("Error setting AudioTrack: play");
512
513 env->ExceptionDescribe();
514 env->ExceptionClear();
515 }
516 }
517
setAudioStop()518 void JNI::setAudioStop() {
519 JNIEnv *env = JNI::getEnv();
520
521 env->CallVoidMethod(_jobj_audio_track, _MID_AudioTrack_stop);
522
523 if (env->ExceptionCheck()) {
524 LOGE("Error setting AudioTrack: stop");
525
526 env->ExceptionDescribe();
527 env->ExceptionClear();
528 }
529 }
530
531 // natives for the dark side
532
create(JNIEnv * env,jobject self,jobject asset_manager,jobject egl,jobject egl_display,jobject at,jint audio_sample_rate,jint audio_buffer_size)533 void JNI::create(JNIEnv *env, jobject self, jobject asset_manager,
534 jobject egl, jobject egl_display,
535 jobject at, jint audio_sample_rate, jint audio_buffer_size) {
536 LOGI("%s", gScummVMFullVersion);
537
538 assert(!_system);
539
540 pause = false;
541 // initial value of zero!
542 sem_init(&pause_sem, 0, 0);
543
544 _asset_archive = new AndroidAssetArchive(asset_manager);
545 assert(_asset_archive);
546
547 _system = new OSystem_Android(audio_sample_rate, audio_buffer_size);
548 assert(_system);
549
550 // weak global ref to allow class to be unloaded
551 // ... except dalvik implements NewWeakGlobalRef only on froyo
552 //_jobj = env->NewWeakGlobalRef(self);
553
554 _jobj = env->NewGlobalRef(self);
555
556 jclass cls = env->GetObjectClass(_jobj);
557
558 #define FIND_METHOD(prefix, name, signature) do { \
559 _MID_ ## prefix ## name = env->GetMethodID(cls, #name, signature); \
560 if (_MID_ ## prefix ## name == 0) \
561 return; \
562 } while (0)
563
564 FIND_METHOD(, setWindowCaption, "(Ljava/lang/String;)V");
565 FIND_METHOD(, getDPI, "([F)V");
566 FIND_METHOD(, displayMessageOnOSD, "(Ljava/lang/String;)V");
567 FIND_METHOD(, openUrl, "(Ljava/lang/String;)V");
568 FIND_METHOD(, hasTextInClipboard, "()Z");
569 FIND_METHOD(, getTextFromClipboard, "()Ljava/lang/String;");
570 FIND_METHOD(, setTextInClipboard, "(Ljava/lang/String;)Z");
571 FIND_METHOD(, isConnectionLimited, "()Z");
572 FIND_METHOD(, showVirtualKeyboard, "(Z)V");
573 FIND_METHOD(, showKeyboardControl, "(Z)V");
574 FIND_METHOD(, getSysArchives, "()[Ljava/lang/String;");
575 FIND_METHOD(, getAllStorageLocations, "()[Ljava/lang/String;");
576 FIND_METHOD(, initSurface, "()Ljavax/microedition/khronos/egl/EGLSurface;");
577 FIND_METHOD(, deinitSurface, "()V");
578 #ifndef BACKEND_ANDROID3D
579 FIND_METHOD(, showSAFRevokePermsControl, "(Z)V");
580 FIND_METHOD(, createDirectoryWithSAF, "(Ljava/lang/String;)Z");
581 FIND_METHOD(, createFileWithSAF, "(Ljava/lang/String;)Ljava/lang/String;");
582 FIND_METHOD(, closeFileWithSAF, "(Ljava/lang/String;)V");
583 FIND_METHOD(, isDirectoryWritableWithSAF, "(Ljava/lang/String;)Z");
584 #endif
585
586 _jobj_egl = env->NewGlobalRef(egl);
587 _jobj_egl_display = env->NewGlobalRef(egl_display);
588
589 cls = env->GetObjectClass(_jobj_egl);
590
591 FIND_METHOD(EGL10_, eglSwapBuffers,
592 "(Ljavax/microedition/khronos/egl/EGLDisplay;"
593 "Ljavax/microedition/khronos/egl/EGLSurface;)Z");
594
595 _jobj_audio_track = env->NewGlobalRef(at);
596
597 cls = env->GetObjectClass(_jobj_audio_track);
598
599 FIND_METHOD(AudioTrack_, flush, "()V");
600 FIND_METHOD(AudioTrack_, pause, "()V");
601 FIND_METHOD(AudioTrack_, play, "()V");
602 FIND_METHOD(AudioTrack_, stop, "()V");
603 FIND_METHOD(AudioTrack_, write, "([BII)I");
604
605 #undef FIND_METHOD
606
607 g_system = _system;
608 }
609
destroy(JNIEnv * env,jobject self)610 void JNI::destroy(JNIEnv *env, jobject self) {
611 delete _asset_archive;
612 _asset_archive = 0;
613
614 // _system is a pointer of OSystem_Android <--- ModularBackend <--- BaseBacked <--- Common::OSystem
615 // It's better to call destroy() rather than just delete here
616 // to avoid mutex issues if a Common::String is used after this point
617 _system->destroy();
618
619 g_system = 0;
620 _system = 0;
621
622 sem_destroy(&pause_sem);
623
624 // see above
625 //JNI::getEnv()->DeleteWeakGlobalRef(_jobj);
626
627 JNI::getEnv()->DeleteGlobalRef(_jobj_egl_display);
628 JNI::getEnv()->DeleteGlobalRef(_jobj_egl);
629 JNI::getEnv()->DeleteGlobalRef(_jobj_audio_track);
630 JNI::getEnv()->DeleteGlobalRef(_jobj);
631 }
632
setSurface(JNIEnv * env,jobject self,jint width,jint height)633 void JNI::setSurface(JNIEnv *env, jobject self, jint width, jint height) {
634 egl_surface_width = width;
635 egl_surface_height = height;
636 surface_changeid++;
637 }
638
main(JNIEnv * env,jobject self,jobjectArray args)639 jint JNI::main(JNIEnv *env, jobject self, jobjectArray args) {
640 assert(_system);
641
642 const int MAX_NARGS = 32;
643 int res = -1;
644
645 int argc = env->GetArrayLength(args);
646 if (argc > MAX_NARGS) {
647 throwByName(env, "java/lang/IllegalArgumentException",
648 "too many arguments");
649 return 0;
650 }
651
652 char *argv[MAX_NARGS];
653
654 // note use in cleanup loop below
655 int nargs;
656
657 for (nargs = 0; nargs < argc; ++nargs) {
658 jstring arg = (jstring)env->GetObjectArrayElement(args, nargs);
659
660 if (arg == 0) {
661 argv[nargs] = 0;
662 } else {
663 const char *cstr = env->GetStringUTFChars(arg, 0);
664
665 argv[nargs] = const_cast<char *>(cstr);
666
667 // exception already thrown?
668 if (cstr == 0)
669 goto cleanup;
670 }
671
672 env->DeleteLocalRef(arg);
673 }
674
675 LOGI("Entering scummvm_main with %d args", argc);
676
677 res = scummvm_main(argc, argv);
678
679 LOGI("scummvm_main exited with code %d", res);
680
681 _system->quit();
682
683 cleanup:
684 nargs--;
685
686 for (int i = 0; i < nargs; ++i) {
687 if (argv[i] == 0)
688 continue;
689
690 jstring arg = (jstring)env->GetObjectArrayElement(args, nargs);
691
692 // Exception already thrown?
693 if (arg == 0)
694 return res;
695
696 env->ReleaseStringUTFChars(arg, argv[i]);
697 env->DeleteLocalRef(arg);
698 }
699
700 return res;
701 }
702
pushEvent(JNIEnv * env,jobject self,int type,int arg1,int arg2,int arg3,int arg4,int arg5,int arg6)703 void JNI::pushEvent(JNIEnv *env, jobject self, int type, int arg1, int arg2,
704 int arg3, int arg4, int arg5, int arg6) {
705 // drop events until we're ready and after we quit
706 if (!_ready_for_events) {
707 LOGW("dropping event");
708 return;
709 }
710
711 assert(_system);
712
713 _system->pushEvent(type, arg1, arg2, arg3, arg4, arg5, arg6);
714 }
715
setPause(JNIEnv * env,jobject self,jboolean value)716 void JNI::setPause(JNIEnv *env, jobject self, jboolean value) {
717 if (!_system)
718 return;
719
720 if (g_engine) {
721 LOGD("pauseEngine: %d", value);
722
723 if (value)
724 JNI::_pauseToken = g_engine->pauseEngine();
725 else
726 JNI::_pauseToken.clear();
727
728 #ifdef BACKEND_ANDROID3D
729 if (value &&
730 g_engine->hasFeature(Engine::kSupportsSavingDuringRuntime) &&
731 g_engine->canSaveGameStateCurrently())
732 g_engine->saveGameState(0, "Android parachute");
733 #endif
734 }
735
736 pause = value;
737
738 if (!pause) {
739 // wake up all threads
740 for (uint i = 0; i < 3; ++i)
741 sem_post(&pause_sem);
742 }
743 }
744
745
getNativeVersionInfo(JNIEnv * env,jobject self)746 jstring JNI::getNativeVersionInfo(JNIEnv *env, jobject self) {
747 return convertToJString(env, Common::U32String(gScummVMVersion));
748 }
749
convertToJString(JNIEnv * env,const Common::U32String & str)750 jstring JNI::convertToJString(JNIEnv *env, const Common::U32String &str) {
751 uint len = 0;
752 uint16 *u16str = str.encodeUTF16Native(&len);
753 jstring jstr = env->NewString(u16str, len);
754 delete[] u16str;
755 return jstr;
756 }
757
convertFromJString(JNIEnv * env,const jstring & jstr)758 Common::U32String JNI::convertFromJString(JNIEnv *env, const jstring &jstr) {
759 const uint16 *utf16Str = env->GetStringChars(jstr, 0);
760 uint jcount = env->GetStringLength(jstr);
761 if (!utf16Str)
762 return Common::U32String();
763 Common::U32String str = Common::U32String::decodeUTF16Native(utf16Str, jcount);
764 env->ReleaseStringChars(jstr, utf16Str);
765
766 return str;
767 }
768
769 // TODO should this be a U32String array?
getAllStorageLocations()770 Common::Array<Common::String> JNI::getAllStorageLocations() {
771 Common::Array<Common::String> *res = new Common::Array<Common::String>();
772
773 JNIEnv *env = JNI::getEnv();
774
775 jobjectArray array =
776 (jobjectArray)env->CallObjectMethod(_jobj, _MID_getAllStorageLocations);
777
778 if (env->ExceptionCheck()) {
779 LOGE("Error finding system archive path");
780
781 env->ExceptionDescribe();
782 env->ExceptionClear();
783
784 return *res;
785 }
786
787 jsize size = env->GetArrayLength(array);
788 for (jsize i = 0; i < size; ++i) {
789 jstring path_obj = (jstring)env->GetObjectArrayElement(array, i);
790 const char *path = env->GetStringUTFChars(path_obj, 0);
791
792 if (path != 0) {
793 res->push_back(path);
794 env->ReleaseStringUTFChars(path_obj, path);
795 }
796
797 env->DeleteLocalRef(path_obj);
798 }
799
800 return *res;
801 }
802
createDirectoryWithSAF(const Common::String & dirPath)803 bool JNI::createDirectoryWithSAF(const Common::String &dirPath) {
804 #ifndef BACKEND_ANDROID3D
805 JNIEnv *env = JNI::getEnv();
806 jstring javaDirPath = env->NewStringUTF(dirPath.c_str());
807
808 bool created = env->CallBooleanMethod(_jobj, _MID_createDirectoryWithSAF, javaDirPath);
809
810 if (env->ExceptionCheck()) {
811 LOGE("JNI - Failed to create directory with SAF enhanced method");
812
813 env->ExceptionDescribe();
814 env->ExceptionClear();
815 created = false;
816 }
817
818 return created;
819 #else
820 return false;
821 #endif
822 }
823
createFileWithSAF(const Common::String & filePath)824 Common::U32String JNI::createFileWithSAF(const Common::String &filePath) {
825 #ifndef BACKEND_ANDROID3D
826 JNIEnv *env = JNI::getEnv();
827 jstring javaFilePath = env->NewStringUTF(filePath.c_str());
828
829 jstring hackyFilenameJSTR = (jstring)env->CallObjectMethod(_jobj, _MID_createFileWithSAF, javaFilePath);
830
831
832 if (env->ExceptionCheck()) {
833 LOGE("JNI - Failed to create file with SAF enhanced method");
834
835 env->ExceptionDescribe();
836 env->ExceptionClear();
837 hackyFilenameJSTR = env->NewStringUTF("");
838 }
839
840 Common::U32String hackyFilenameStr = convertFromJString(env, hackyFilenameJSTR);
841
842 env->DeleteLocalRef(hackyFilenameJSTR);
843
844 return hackyFilenameStr;
845 #else
846 return Common::U32String();
847 #endif
848 }
849
closeFileWithSAF(const Common::String & hackyFilename)850 void JNI::closeFileWithSAF(const Common::String &hackyFilename) {
851 #ifndef BACKEND_ANDROID3D
852 JNIEnv *env = JNI::getEnv();
853 jstring javaHackyFilename = env->NewStringUTF(hackyFilename.c_str());
854
855 env->CallVoidMethod(_jobj, _MID_closeFileWithSAF, javaHackyFilename);
856
857 if (env->ExceptionCheck()) {
858 LOGE("JNI - Failed to close file with SAF enhanced method");
859
860 env->ExceptionDescribe();
861 env->ExceptionClear();
862 }
863 #endif
864 }
865
isDirectoryWritableWithSAF(const Common::String & dirPath)866 bool JNI::isDirectoryWritableWithSAF(const Common::String &dirPath) {
867 #ifndef BACKEND_ANDROID3D
868 JNIEnv *env = JNI::getEnv();
869 jstring javaDirPath = env->NewStringUTF(dirPath.c_str());
870
871 bool isWritable = env->CallBooleanMethod(_jobj, _MID_isDirectoryWritableWithSAF, javaDirPath);
872
873 if (env->ExceptionCheck()) {
874 LOGE("JNI - Failed to check if directory is writable SAF enhanced method");
875
876 env->ExceptionDescribe();
877 env->ExceptionClear();
878 isWritable = false;
879 }
880
881 return isWritable;
882 #else
883 return false;
884 #endif
885 }
886
887 #endif
888
889