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