00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023 #if defined(__ANDROID__)
00024
00025
00026 #define FORBIDDEN_SYMBOL_EXCEPTION_time_h
00027 #define FORBIDDEN_SYMBOL_EXCEPTION_abort
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041 #define FORBIDDEN_SYMBOL_EXCEPTION_printf
00042
00043 #include "base/main.h"
00044 #include "base/version.h"
00045 #include "common/config-manager.h"
00046 #include "common/error.h"
00047 #include "common/textconsole.h"
00048 #include "engines/engine.h"
00049
00050 #include "backends/platform/android/android.h"
00051 #include "backends/platform/android/asset-archive.h"
00052 #include "backends/platform/android/jni.h"
00053
00054 __attribute__ ((visibility("default")))
00055 jint JNICALL JNI_OnLoad(JavaVM *vm, void *) {
00056 return JNI::onLoad(vm);
00057 }
00058
00059 JavaVM *JNI::_vm = 0;
00060 jobject JNI::_jobj = 0;
00061 jobject JNI::_jobj_audio_track = 0;
00062 jobject JNI::_jobj_egl = 0;
00063 jobject JNI::_jobj_egl_display = 0;
00064 jobject JNI::_jobj_egl_surface = 0;
00065
00066 Common::Archive *JNI::_asset_archive = 0;
00067 OSystem_Android *JNI::_system = 0;
00068
00069 bool JNI::pause = false;
00070 sem_t JNI::pause_sem = { 0 };
00071
00072 int JNI::surface_changeid = 0;
00073 int JNI::egl_surface_width = 0;
00074 int JNI::egl_surface_height = 0;
00075 bool JNI::_ready_for_events = 0;
00076
00077 jmethodID JNI::_MID_getDPI = 0;
00078 jmethodID JNI::_MID_displayMessageOnOSD = 0;
00079 jmethodID JNI::_MID_openUrl = 0;
00080 jmethodID JNI::_MID_isConnectionLimited = 0;
00081 jmethodID JNI::_MID_setWindowCaption = 0;
00082 jmethodID JNI::_MID_showVirtualKeyboard = 0;
00083 jmethodID JNI::_MID_getSysArchives = 0;
00084 jmethodID JNI::_MID_initSurface = 0;
00085 jmethodID JNI::_MID_deinitSurface = 0;
00086
00087 jmethodID JNI::_MID_EGL10_eglSwapBuffers = 0;
00088
00089 jmethodID JNI::_MID_AudioTrack_flush = 0;
00090 jmethodID JNI::_MID_AudioTrack_pause = 0;
00091 jmethodID JNI::_MID_AudioTrack_play = 0;
00092 jmethodID JNI::_MID_AudioTrack_stop = 0;
00093 jmethodID JNI::_MID_AudioTrack_write = 0;
00094
00095 const JNINativeMethod JNI::_natives[] = {
00096 { "create", "(Landroid/content/res/AssetManager;"
00097 "Ljavax/microedition/khronos/egl/EGL10;"
00098 "Ljavax/microedition/khronos/egl/EGLDisplay;"
00099 "Landroid/media/AudioTrack;II)V",
00100 (void *)JNI::create },
00101 { "destroy", "()V",
00102 (void *)JNI::destroy },
00103 { "setSurface", "(II)V",
00104 (void *)JNI::setSurface },
00105 { "main", "([Ljava/lang/String;)I",
00106 (void *)JNI::main },
00107 { "pushEvent", "(IIIIIII)V",
00108 (void *)JNI::pushEvent },
00109 { "enableZoning", "(Z)V",
00110 (void *)JNI::enableZoning },
00111 { "setPause", "(Z)V",
00112 (void *)JNI::setPause }
00113 };
00114
00115 JNI::JNI() {
00116 }
00117
00118 JNI::~JNI() {
00119 }
00120
00121 jint JNI::onLoad(JavaVM *vm) {
00122 _vm = vm;
00123
00124 JNIEnv *env;
00125
00126 if (_vm->GetEnv((void **)&env, JNI_VERSION_1_2))
00127 return JNI_ERR;
00128
00129 jclass cls = env->FindClass("org/residualvm/residualvm/ResidualVM");
00130 if (cls == 0)
00131 return JNI_ERR;
00132
00133 if (env->RegisterNatives(cls, _natives, ARRAYSIZE(_natives)) < 0)
00134 return JNI_ERR;
00135
00136 return JNI_VERSION_1_2;
00137 }
00138
00139 JNIEnv *JNI::getEnv() {
00140 JNIEnv *env = 0;
00141
00142 jint res = _vm->GetEnv((void **)&env, JNI_VERSION_1_2);
00143
00144 if (res != JNI_OK) {
00145 LOGE("GetEnv() failed: %d", res);
00146 abort();
00147 }
00148
00149 return env;
00150 }
00151
00152 void JNI::attachThread() {
00153 JNIEnv *env = 0;
00154
00155 jint res = _vm->AttachCurrentThread(&env, 0);
00156
00157 if (res != JNI_OK) {
00158 LOGE("AttachCurrentThread() failed: %d", res);
00159 abort();
00160 }
00161 }
00162
00163 void JNI::detachThread() {
00164 jint res = _vm->DetachCurrentThread();
00165
00166 if (res != JNI_OK) {
00167 LOGE("DetachCurrentThread() failed: %d", res);
00168 abort();
00169 }
00170 }
00171
00172 void JNI::setReadyForEvents(bool ready) {
00173 _ready_for_events = ready;
00174 }
00175
00176 void JNI::throwByName(JNIEnv *env, const char *name, const char *msg) {
00177 jclass cls = env->FindClass(name);
00178
00179
00180 if (cls != 0)
00181 env->ThrowNew(cls, msg);
00182
00183 env->DeleteLocalRef(cls);
00184 }
00185
00186 void JNI::throwRuntimeException(JNIEnv *env, const char *msg) {
00187 throwByName(env, "java/lang/RuntimeException", msg);
00188 }
00189
00190
00191
00192 void JNI::getDPI(float *values) {
00193 values[0] = 0.0;
00194 values[1] = 0.0;
00195
00196 JNIEnv *env = JNI::getEnv();
00197
00198 jfloatArray array = env->NewFloatArray(2);
00199
00200 env->CallVoidMethod(_jobj, _MID_getDPI, array);
00201
00202 if (env->ExceptionCheck()) {
00203 LOGE("Failed to get DPIs");
00204
00205 env->ExceptionDescribe();
00206 env->ExceptionClear();
00207 } else {
00208 jfloat *res = env->GetFloatArrayElements(array, 0);
00209
00210 if (res) {
00211 values[0] = res[0];
00212 values[1] = res[1];
00213
00214 env->ReleaseFloatArrayElements(array, res, 0);
00215 }
00216 }
00217
00218 env->DeleteLocalRef(array);
00219 }
00220
00221 void JNI::displayMessageOnOSD(const char *msg) {
00222 JNIEnv *env = JNI::getEnv();
00223 jstring java_msg = env->NewStringUTF(msg);
00224
00225 env->CallVoidMethod(_jobj, _MID_displayMessageOnOSD, java_msg);
00226
00227 if (env->ExceptionCheck()) {
00228 LOGE("Failed to display OSD message");
00229
00230 env->ExceptionDescribe();
00231 env->ExceptionClear();
00232 }
00233
00234 env->DeleteLocalRef(java_msg);
00235 }
00236
00237 bool JNI::openUrl(const char *url) {
00238 bool success = true;
00239 JNIEnv *env = JNI::getEnv();
00240 jstring javaUrl = env->NewStringUTF(url);
00241
00242 env->CallVoidMethod(_jobj, _MID_openUrl, javaUrl);
00243
00244 if (env->ExceptionCheck()) {
00245 LOGE("Failed to open URL");
00246
00247 env->ExceptionDescribe();
00248 env->ExceptionClear();
00249 success = false;
00250 }
00251
00252 env->DeleteLocalRef(javaUrl);
00253 return success;
00254 }
00255
00256 bool JNI::isConnectionLimited() {
00257 bool limited = false;
00258 JNIEnv *env = JNI::getEnv();
00259 limited = env->CallBooleanMethod(_jobj, _MID_isConnectionLimited);
00260
00261 if (env->ExceptionCheck()) {
00262 LOGE("Failed to check whether connection's limited");
00263
00264 env->ExceptionDescribe();
00265 env->ExceptionClear();
00266 limited = true;
00267 }
00268
00269 return limited;
00270 }
00271
00272 void JNI::setWindowCaption(const char *caption) {
00273 JNIEnv *env = JNI::getEnv();
00274 jstring java_caption = env->NewStringUTF(caption);
00275
00276 env->CallVoidMethod(_jobj, _MID_setWindowCaption, java_caption);
00277
00278 if (env->ExceptionCheck()) {
00279 LOGE("Failed to set window caption");
00280
00281 env->ExceptionDescribe();
00282 env->ExceptionClear();
00283 }
00284
00285 env->DeleteLocalRef(java_caption);
00286 }
00287
00288 void JNI::showVirtualKeyboard(bool enable) {
00289 JNIEnv *env = JNI::getEnv();
00290
00291 env->CallVoidMethod(_jobj, _MID_showVirtualKeyboard, enable);
00292
00293 if (env->ExceptionCheck()) {
00294 LOGE("Error trying to show virtual keyboard");
00295
00296 env->ExceptionDescribe();
00297 env->ExceptionClear();
00298 }
00299 }
00300
00301 void JNI::addSysArchivesToSearchSet(Common::SearchSet &s, int priority) {
00302 JNIEnv *env = JNI::getEnv();
00303
00304 s.add("ASSET", _asset_archive, priority, false);
00305
00306 jobjectArray array =
00307 (jobjectArray)env->CallObjectMethod(_jobj, _MID_getSysArchives);
00308
00309 if (env->ExceptionCheck()) {
00310 LOGE("Error finding system archive path");
00311
00312 env->ExceptionDescribe();
00313 env->ExceptionClear();
00314
00315 return;
00316 }
00317
00318 jsize size = env->GetArrayLength(array);
00319 for (jsize i = 0; i < size; ++i) {
00320 jstring path_obj = (jstring)env->GetObjectArrayElement(array, i);
00321 const char *path = env->GetStringUTFChars(path_obj, 0);
00322
00323 if (path != 0) {
00324 s.addDirectory(path, path, priority);
00325 env->ReleaseStringUTFChars(path_obj, path);
00326 }
00327
00328 env->DeleteLocalRef(path_obj);
00329 }
00330 }
00331
00332 bool JNI::initSurface() {
00333 JNIEnv *env = JNI::getEnv();
00334
00335 jobject obj = env->CallObjectMethod(_jobj, _MID_initSurface);
00336
00337 if (!obj || env->ExceptionCheck()) {
00338 LOGE("initSurface failed");
00339
00340 env->ExceptionDescribe();
00341 env->ExceptionClear();
00342
00343 return false;
00344 }
00345
00346 _jobj_egl_surface = env->NewGlobalRef(obj);
00347
00348 return true;
00349 }
00350
00351 void JNI::deinitSurface() {
00352 JNIEnv *env = JNI::getEnv();
00353
00354 env->CallVoidMethod(_jobj, _MID_deinitSurface);
00355
00356 if (env->ExceptionCheck()) {
00357 LOGE("deinitSurface failed");
00358
00359 env->ExceptionDescribe();
00360 env->ExceptionClear();
00361 }
00362
00363 env->DeleteGlobalRef(_jobj_egl_surface);
00364 _jobj_egl_surface = 0;
00365 }
00366
00367 void JNI::setAudioPause() {
00368 JNIEnv *env = JNI::getEnv();
00369
00370 env->CallVoidMethod(_jobj_audio_track, _MID_AudioTrack_flush);
00371
00372 if (env->ExceptionCheck()) {
00373 LOGE("Error flushing AudioTrack");
00374
00375 env->ExceptionDescribe();
00376 env->ExceptionClear();
00377 }
00378
00379 env->CallVoidMethod(_jobj_audio_track, _MID_AudioTrack_pause);
00380
00381 if (env->ExceptionCheck()) {
00382 LOGE("Error setting AudioTrack: pause");
00383
00384 env->ExceptionDescribe();
00385 env->ExceptionClear();
00386 }
00387 }
00388
00389 void JNI::setAudioPlay() {
00390 JNIEnv *env = JNI::getEnv();
00391
00392 env->CallVoidMethod(_jobj_audio_track, _MID_AudioTrack_play);
00393
00394 if (env->ExceptionCheck()) {
00395 LOGE("Error setting AudioTrack: play");
00396
00397 env->ExceptionDescribe();
00398 env->ExceptionClear();
00399 }
00400 }
00401
00402 void JNI::setAudioStop() {
00403 JNIEnv *env = JNI::getEnv();
00404
00405 env->CallVoidMethod(_jobj_audio_track, _MID_AudioTrack_stop);
00406
00407 if (env->ExceptionCheck()) {
00408 LOGE("Error setting AudioTrack: stop");
00409
00410 env->ExceptionDescribe();
00411 env->ExceptionClear();
00412 }
00413 }
00414
00415
00416
00417 void JNI::create(JNIEnv *env, jobject self, jobject asset_manager,
00418 jobject egl, jobject egl_display,
00419 jobject at, jint audio_sample_rate, jint audio_buffer_size) {
00420 LOGI(gScummVMFullVersion);
00421
00422 assert(!_system);
00423
00424 pause = false;
00425
00426 sem_init(&pause_sem, 0, 0);
00427
00428 _asset_archive = new AndroidAssetArchive(asset_manager);
00429 assert(_asset_archive);
00430
00431 _system = new OSystem_Android(audio_sample_rate, audio_buffer_size);
00432 assert(_system);
00433
00434
00435
00436
00437
00438 _jobj = env->NewGlobalRef(self);
00439
00440 jclass cls = env->GetObjectClass(_jobj);
00441
00442 #define FIND_METHOD(prefix, name, signature) do { \
00443 _MID_ ## prefix ## name = env->GetMethodID(cls, #name, signature); \
00444 if (_MID_ ## prefix ## name == 0) \
00445 return; \
00446 } while (0)
00447
00448 FIND_METHOD(, setWindowCaption, "(Ljava/lang/String;)V");
00449 FIND_METHOD(, getDPI, "([F)V");
00450 FIND_METHOD(, displayMessageOnOSD, "(Ljava/lang/String;)V");
00451 FIND_METHOD(, openUrl, "(Ljava/lang/String;)V");
00452 FIND_METHOD(, isConnectionLimited, "()Z");
00453 FIND_METHOD(, showVirtualKeyboard, "(Z)V");
00454 FIND_METHOD(, getSysArchives, "()[Ljava/lang/String;");
00455 FIND_METHOD(, initSurface, "()Ljavax/microedition/khronos/egl/EGLSurface;");
00456 FIND_METHOD(, deinitSurface, "()V");
00457
00458 _jobj_egl = env->NewGlobalRef(egl);
00459 _jobj_egl_display = env->NewGlobalRef(egl_display);
00460
00461 cls = env->GetObjectClass(_jobj_egl);
00462
00463 FIND_METHOD(EGL10_, eglSwapBuffers,
00464 "(Ljavax/microedition/khronos/egl/EGLDisplay;"
00465 "Ljavax/microedition/khronos/egl/EGLSurface;)Z");
00466
00467 _jobj_audio_track = env->NewGlobalRef(at);
00468
00469 cls = env->GetObjectClass(_jobj_audio_track);
00470
00471 FIND_METHOD(AudioTrack_, flush, "()V");
00472 FIND_METHOD(AudioTrack_, pause, "()V");
00473 FIND_METHOD(AudioTrack_, play, "()V");
00474 FIND_METHOD(AudioTrack_, stop, "()V");
00475 FIND_METHOD(AudioTrack_, write, "([BII)I");
00476
00477 #undef FIND_METHOD
00478
00479 g_system = _system;
00480 }
00481
00482 void JNI::destroy(JNIEnv *env, jobject self) {
00483 delete _asset_archive;
00484 _asset_archive = 0;
00485
00486 delete _system;
00487 g_system = 0;
00488 _system = 0;
00489
00490 sem_destroy(&pause_sem);
00491
00492
00493
00494
00495 JNI::getEnv()->DeleteGlobalRef(_jobj_egl_display);
00496 JNI::getEnv()->DeleteGlobalRef(_jobj_egl);
00497 JNI::getEnv()->DeleteGlobalRef(_jobj_audio_track);
00498 JNI::getEnv()->DeleteGlobalRef(_jobj);
00499 }
00500
00501 void JNI::setSurface(JNIEnv *env, jobject self, jint width, jint height) {
00502 egl_surface_width = width;
00503 egl_surface_height = height;
00504 surface_changeid++;
00505 }
00506
00507 jint JNI::main(JNIEnv *env, jobject self, jobjectArray args) {
00508 assert(_system);
00509
00510 const int MAX_NARGS = 32;
00511 int res = -1;
00512
00513 int argc = env->GetArrayLength(args);
00514 if (argc > MAX_NARGS) {
00515 throwByName(env, "java/lang/IllegalArgumentException",
00516 "too many arguments");
00517 return 0;
00518 }
00519
00520 char *argv[MAX_NARGS];
00521
00522
00523 int nargs;
00524
00525 for (nargs = 0; nargs < argc; ++nargs) {
00526 jstring arg = (jstring)env->GetObjectArrayElement(args, nargs);
00527
00528 if (arg == 0) {
00529 argv[nargs] = 0;
00530 } else {
00531 const char *cstr = env->GetStringUTFChars(arg, 0);
00532
00533 argv[nargs] = const_cast<char *>(cstr);
00534
00535
00536 if (cstr == 0)
00537 goto cleanup;
00538 }
00539
00540 env->DeleteLocalRef(arg);
00541 }
00542
00543 LOGI("Entering scummvm_main with %d args", argc);
00544
00545 res = scummvm_main(argc, argv);
00546
00547 LOGI("scummvm_main exited with code %d", res);
00548
00549 _system->quit();
00550
00551 cleanup:
00552 nargs--;
00553
00554 for (int i = 0; i < nargs; ++i) {
00555 if (argv[i] == 0)
00556 continue;
00557
00558 jstring arg = (jstring)env->GetObjectArrayElement(args, nargs);
00559
00560
00561 if (arg == 0)
00562 return res;
00563
00564 env->ReleaseStringUTFChars(arg, argv[i]);
00565 env->DeleteLocalRef(arg);
00566 }
00567
00568 return res;
00569 }
00570
00571 void JNI::pushEvent(JNIEnv *env, jobject self, int type, int arg1, int arg2,
00572 int arg3, int arg4, int arg5, int arg6) {
00573
00574 if (!_ready_for_events) {
00575 LOGW("dropping event");
00576 return;
00577 }
00578
00579 assert(_system);
00580
00581 _system->pushEvent(type, arg1, arg2, arg3, arg4, arg5, arg6);
00582 }
00583
00584 void JNI::enableZoning(JNIEnv *env, jobject self, jboolean enable) {
00585 assert(_system);
00586
00587 _system->enableZoning(enable);
00588 }
00589
00590 void JNI::setPause(JNIEnv *env, jobject self, jboolean value) {
00591 if (!_system)
00592 return;
00593
00594 if (g_engine) {
00595 LOGD("pauseEngine: %d", value);
00596
00597 g_engine->pauseEngine(value);
00598
00599 if (value &&
00600 g_engine->hasFeature(Engine::kSupportsSavingDuringRuntime) &&
00601 g_engine->canSaveGameStateCurrently())
00602 g_engine->saveGameState(0, "Android parachute");
00603 }
00604
00605 pause = value;
00606
00607 if (!pause) {
00608
00609 for (uint i = 0; i < 3; ++i)
00610 sem_post(&pause_sem);
00611 }
00612 }
00613
00614 #endif