Logo Search packages:      
Sourcecode: scummvm version File versions

jni.cpp

/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.      See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * $URL$
 * $Id$
 *
 */

#if defined(__ANDROID__)

#include "base/main.h"
#include "base/version.h"
#include "common/config-manager.h"
#include "common/error.h"
#include "common/textconsole.h"
#include "engines/engine.h"

#include "backends/platform/android/android.h"
#include "backends/platform/android/asset-archive.h"
#include "backends/platform/android/jni.h"

__attribute__ ((visibility("default")))
jint JNICALL JNI_OnLoad(JavaVM *vm, void *) {
      return JNI::onLoad(vm);
}

JavaVM *JNI::_vm = 0;
jobject JNI::_jobj = 0;
jobject JNI::_jobj_audio_track = 0;
jobject JNI::_jobj_egl = 0;
jobject JNI::_jobj_egl_display = 0;
jobject JNI::_jobj_egl_surface = 0;

Common::Archive *JNI::_asset_archive = 0;
OSystem_Android *JNI::_system = 0;

bool JNI::pause = false;
sem_t JNI::pause_sem = { 0 };

int JNI::surface_changeid = 0;
int JNI::egl_surface_width = 0;
int JNI::egl_surface_height = 0;
bool JNI::_ready_for_events = 0;

jmethodID JNI::_MID_getDPI = 0;
jmethodID JNI::_MID_displayMessageOnOSD = 0;
jmethodID JNI::_MID_setWindowCaption = 0;
jmethodID JNI::_MID_showVirtualKeyboard = 0;
jmethodID JNI::_MID_getSysArchives = 0;
jmethodID JNI::_MID_getPluginDirectories = 0;
jmethodID JNI::_MID_initSurface = 0;
jmethodID JNI::_MID_deinitSurface = 0;

jmethodID JNI::_MID_EGL10_eglSwapBuffers = 0;

jmethodID JNI::_MID_AudioTrack_flush = 0;
jmethodID JNI::_MID_AudioTrack_pause = 0;
jmethodID JNI::_MID_AudioTrack_play = 0;
jmethodID JNI::_MID_AudioTrack_stop = 0;
jmethodID JNI::_MID_AudioTrack_write = 0;

const JNINativeMethod JNI::_natives[] = {
      { "create", "(Landroid/content/res/AssetManager;"
                        "Ljavax/microedition/khronos/egl/EGL10;"
                        "Ljavax/microedition/khronos/egl/EGLDisplay;"
                        "Landroid/media/AudioTrack;II)V",
            (void *)JNI::create },
      { "destroy", "()V",
            (void *)JNI::destroy },
      { "setSurface", "(II)V",
            (void *)JNI::setSurface },
      { "main", "([Ljava/lang/String;)I",
            (void *)JNI::main },
      { "pushEvent", "(IIIIII)V",
            (void *)JNI::pushEvent },
      { "enableZoning", "(Z)V",
            (void *)JNI::enableZoning },
      { "setPause", "(Z)V",
            (void *)JNI::setPause }
};

JNI::JNI() {
}

JNI::~JNI() {
}

jint JNI::onLoad(JavaVM *vm) {
      _vm = vm;

      JNIEnv *env;

      if (_vm->GetEnv((void **)&env, JNI_VERSION_1_2))
            return JNI_ERR;

      jclass cls = env->FindClass("org/inodes/gus/scummvm/ScummVM");
      if (cls == 0)
            return JNI_ERR;

      if (env->RegisterNatives(cls, _natives, ARRAYSIZE(_natives)) < 0)
            return JNI_ERR;

      return JNI_VERSION_1_2;
}

JNIEnv *JNI::getEnv() {
      JNIEnv *env = 0;

      jint res = _vm->GetEnv((void **)&env, JNI_VERSION_1_2);

      if (res != JNI_OK) {
            LOGE("GetEnv() failed: %d", res);
            abort();
      }

      return env;
}

void JNI::attachThread() {
      JNIEnv *env = 0;

      jint res = _vm->AttachCurrentThread(&env, 0);

      if (res != JNI_OK) {
            LOGE("AttachCurrentThread() failed: %d", res);
            abort();
      }
}

void JNI::detachThread() {
      jint res = _vm->DetachCurrentThread();

      if (res != JNI_OK) {
            LOGE("DetachCurrentThread() failed: %d", res);
            abort();
      }
}

void JNI::setReadyForEvents(bool ready) {
      _ready_for_events = ready;
}

void JNI::throwByName(JNIEnv *env, const char *name, const char *msg) {
      jclass cls = env->FindClass(name);

      // if cls is 0, an exception has already been thrown
      if (cls != 0)
            env->ThrowNew(cls, msg);

      env->DeleteLocalRef(cls);
}

void JNI::throwRuntimeException(JNIEnv *env, const char *msg) {
      throwByName(env, "java/lang/RuntimeException", msg);
}

// calls to the dark side

void JNI::getDPI(float *values) {
      values[0] = 0.0;
      values[1] = 0.0;

      JNIEnv *env = JNI::getEnv();

      jfloatArray array = env->NewFloatArray(2);

      env->CallVoidMethod(_jobj, _MID_getDPI, array);

      if (env->ExceptionCheck()) {
            LOGE("Failed to get DPIs");

            env->ExceptionDescribe();
            env->ExceptionClear();
      } else {
            jfloat *res = env->GetFloatArrayElements(array, 0);

            if (res) {
                  values[0] = res[0];
                  values[1] = res[1];

                  env->ReleaseFloatArrayElements(array, res, 0);
            }
      }

      env->DeleteLocalRef(array);
}

void JNI::displayMessageOnOSD(const char *msg) {
      JNIEnv *env = JNI::getEnv();
      jstring java_msg = env->NewStringUTF(msg);

      env->CallVoidMethod(_jobj, _MID_displayMessageOnOSD, java_msg);

      if (env->ExceptionCheck()) {
            LOGE("Failed to display OSD message");

            env->ExceptionDescribe();
            env->ExceptionClear();
      }

      env->DeleteLocalRef(java_msg);
}

void JNI::setWindowCaption(const char *caption) {
      JNIEnv *env = JNI::getEnv();
      jstring java_caption = env->NewStringUTF(caption);

      env->CallVoidMethod(_jobj, _MID_setWindowCaption, java_caption);

      if (env->ExceptionCheck()) {
            LOGE("Failed to set window caption");

            env->ExceptionDescribe();
            env->ExceptionClear();
      }

      env->DeleteLocalRef(java_caption);
}

void JNI::showVirtualKeyboard(bool enable) {
      JNIEnv *env = JNI::getEnv();

      env->CallVoidMethod(_jobj, _MID_showVirtualKeyboard, enable);

      if (env->ExceptionCheck()) {
            LOGE("Error trying to show virtual keyboard");

            env->ExceptionDescribe();
            env->ExceptionClear();
      }
}

void JNI::addSysArchivesToSearchSet(Common::SearchSet &s, int priority) {
      JNIEnv *env = JNI::getEnv();

      s.add("ASSET", _asset_archive, priority, false);

      jobjectArray array =
            (jobjectArray)env->CallObjectMethod(_jobj, _MID_getSysArchives);

      if (env->ExceptionCheck()) {
            LOGE("Error finding system archive path");

            env->ExceptionDescribe();
            env->ExceptionClear();

            return;
      }

      jsize size = env->GetArrayLength(array);
      for (jsize i = 0; i < size; ++i) {
            jstring path_obj = (jstring)env->GetObjectArrayElement(array, i);
            const char *path = env->GetStringUTFChars(path_obj, 0);

            if (path != 0) {
                  s.addDirectory(path, path, priority);
                  env->ReleaseStringUTFChars(path_obj, path);
            }

            env->DeleteLocalRef(path_obj);
      }
}

void JNI::getPluginDirectories(Common::FSList &dirs) {
      JNIEnv *env = JNI::getEnv();

      jobjectArray array =
            (jobjectArray)env->CallObjectMethod(_jobj, _MID_getPluginDirectories);

      if (env->ExceptionCheck()) {
            LOGE("Error finding plugin directories");

            env->ExceptionDescribe();
            env->ExceptionClear();

            return;
      }

      jsize size = env->GetArrayLength(array);
      for (jsize i = 0; i < size; ++i) {
            jstring path_obj = (jstring)env->GetObjectArrayElement(array, i);

            if (path_obj == 0)
                  continue;

            const char *path = env->GetStringUTFChars(path_obj, 0);

            if (path == 0) {
                  LOGE("Error getting string characters from plugin directory");

                  env->ExceptionClear();
                  env->DeleteLocalRef(path_obj);

                  continue;
            }

            dirs.push_back(Common::FSNode(path));

            env->ReleaseStringUTFChars(path_obj, path);
            env->DeleteLocalRef(path_obj);
      }
}

bool JNI::initSurface() {
      JNIEnv *env = JNI::getEnv();

      jobject obj = env->CallObjectMethod(_jobj, _MID_initSurface);

      if (!obj || env->ExceptionCheck()) {
            LOGE("initSurface failed");

            env->ExceptionDescribe();
            env->ExceptionClear();

            return false;
      }

      _jobj_egl_surface = env->NewGlobalRef(obj);

      return true;
}

void JNI::deinitSurface() {
      JNIEnv *env = JNI::getEnv();

      env->CallVoidMethod(_jobj, _MID_deinitSurface);

      if (env->ExceptionCheck()) {
            LOGE("deinitSurface failed");

            env->ExceptionDescribe();
            env->ExceptionClear();
      }

      env->DeleteGlobalRef(_jobj_egl_surface);
      _jobj_egl_surface = 0;
}

void JNI::setAudioPause() {
      JNIEnv *env = JNI::getEnv();

      env->CallVoidMethod(_jobj_audio_track, _MID_AudioTrack_flush);

      if (env->ExceptionCheck()) {
            LOGE("Error flushing AudioTrack");

            env->ExceptionDescribe();
            env->ExceptionClear();
      }

      env->CallVoidMethod(_jobj_audio_track, _MID_AudioTrack_pause);

      if (env->ExceptionCheck()) {
            LOGE("Error setting AudioTrack: pause");

            env->ExceptionDescribe();
            env->ExceptionClear();
      }
}

void JNI::setAudioPlay() {
      JNIEnv *env = JNI::getEnv();

      env->CallVoidMethod(_jobj_audio_track, _MID_AudioTrack_play);

      if (env->ExceptionCheck()) {
            LOGE("Error setting AudioTrack: play");

            env->ExceptionDescribe();
            env->ExceptionClear();
      }
}

void JNI::setAudioStop() {
      JNIEnv *env = JNI::getEnv();

      env->CallVoidMethod(_jobj_audio_track, _MID_AudioTrack_stop);

      if (env->ExceptionCheck()) {
            LOGE("Error setting AudioTrack: stop");

            env->ExceptionDescribe();
            env->ExceptionClear();
      }
}

// natives for the dark side

void JNI::create(JNIEnv *env, jobject self, jobject asset_manager,
                        jobject egl, jobject egl_display,
                        jobject at, jint audio_sample_rate, jint audio_buffer_size) {
      LOGI(gScummVMFullVersion);

      assert(!_system);

      pause = false;
      // initial value of zero!
      sem_init(&pause_sem, 0, 0);

      _asset_archive = new AndroidAssetArchive(asset_manager);
      assert(_asset_archive);

      _system = new OSystem_Android(audio_sample_rate, audio_buffer_size);
      assert(_system);

      // weak global ref to allow class to be unloaded
      // ... except dalvik implements NewWeakGlobalRef only on froyo
      //_jobj = env->NewWeakGlobalRef(self);

      _jobj = env->NewGlobalRef(self);

      jclass cls = env->GetObjectClass(_jobj);

#define FIND_METHOD(prefix, name, signature) do {                                         \
            _MID_ ## prefix ## name = env->GetMethodID(cls, #name, signature);      \
            if (_MID_ ## prefix ## name == 0)                                                   \
                  return;                                                                                         \
      } while (0)

      FIND_METHOD(, setWindowCaption, "(Ljava/lang/String;)V");
      FIND_METHOD(, getDPI, "([F)V");
      FIND_METHOD(, displayMessageOnOSD, "(Ljava/lang/String;)V");
      FIND_METHOD(, showVirtualKeyboard, "(Z)V");
      FIND_METHOD(, getSysArchives, "()[Ljava/lang/String;");
      FIND_METHOD(, getPluginDirectories, "()[Ljava/lang/String;");
      FIND_METHOD(, initSurface, "()Ljavax/microedition/khronos/egl/EGLSurface;");
      FIND_METHOD(, deinitSurface, "()V");

      _jobj_egl = env->NewGlobalRef(egl);
      _jobj_egl_display = env->NewGlobalRef(egl_display);

      cls = env->GetObjectClass(_jobj_egl);

      FIND_METHOD(EGL10_, eglSwapBuffers,
                        "(Ljavax/microedition/khronos/egl/EGLDisplay;"
                        "Ljavax/microedition/khronos/egl/EGLSurface;)Z");

      _jobj_audio_track = env->NewGlobalRef(at);

      cls = env->GetObjectClass(_jobj_audio_track);

      FIND_METHOD(AudioTrack_, flush, "()V");
      FIND_METHOD(AudioTrack_, pause, "()V");
      FIND_METHOD(AudioTrack_, play, "()V");
      FIND_METHOD(AudioTrack_, stop, "()V");
      FIND_METHOD(AudioTrack_, write, "([BII)I");

#undef FIND_METHOD

      g_system = _system;
}

void JNI::destroy(JNIEnv *env, jobject self) {
      delete _asset_archive;
      _asset_archive = 0;

      delete _system;
      g_system = 0;
      _system = 0;

      sem_destroy(&pause_sem);

      // see above
      //JNI::getEnv()->DeleteWeakGlobalRef(_jobj);

      JNI::getEnv()->DeleteGlobalRef(_jobj_egl_display);
      JNI::getEnv()->DeleteGlobalRef(_jobj_egl);
      JNI::getEnv()->DeleteGlobalRef(_jobj_audio_track);
      JNI::getEnv()->DeleteGlobalRef(_jobj);
}

void JNI::setSurface(JNIEnv *env, jobject self, jint width, jint height) {
      egl_surface_width = width;
      egl_surface_height = height;
      surface_changeid++;
}

jint JNI::main(JNIEnv *env, jobject self, jobjectArray args) {
      assert(_system);

      const int MAX_NARGS = 32;
      int res = -1;

      int argc = env->GetArrayLength(args);
      if (argc > MAX_NARGS) {
            throwByName(env, "java/lang/IllegalArgumentException",
                              "too many arguments");
            return 0;
      }

      char *argv[MAX_NARGS];

      // note use in cleanup loop below
      int nargs;

      for (nargs = 0; nargs < argc; ++nargs) {
            jstring arg = (jstring)env->GetObjectArrayElement(args, nargs);

            if (arg == 0) {
                  argv[nargs] = 0;
            } else {
                  const char *cstr = env->GetStringUTFChars(arg, 0);

                  argv[nargs] = const_cast<char *>(cstr);

                  // exception already thrown?
                  if (cstr == 0)
                        goto cleanup;
            }

            env->DeleteLocalRef(arg);
      }

#ifdef DYNAMIC_MODULES
      PluginManager::instance().addPluginProvider(new AndroidPluginProvider());
#endif

      LOGI("Entering scummvm_main with %d args", argc);

      res = scummvm_main(argc, argv);

      LOGI("scummvm_main exited with code %d", res);

      _system->quit();

cleanup:
      nargs--;

      for (int i = 0; i < nargs; ++i) {
            if (argv[i] == 0)
                  continue;

            jstring arg = (jstring)env->GetObjectArrayElement(args, nargs);

            // Exception already thrown?
            if (arg == 0)
                  return res;

            env->ReleaseStringUTFChars(arg, argv[i]);
            env->DeleteLocalRef(arg);
      }

      return res;
}

void JNI::pushEvent(JNIEnv *env, jobject self, int type, int arg1, int arg2,
                              int arg3, int arg4, int arg5) {
      // drop events until we're ready and after we quit
      if (!_ready_for_events) {
            LOGW("dropping event");
            return;
      }

      assert(_system);

      _system->pushEvent(type, arg1, arg2, arg3, arg4, arg5);
}

void JNI::enableZoning(JNIEnv *env, jobject self, jboolean enable) {
      assert(_system);

      _system->enableZoning(enable);
}

void JNI::setPause(JNIEnv *env, jobject self, jboolean value) {
      if (!_system)
            return;

      if (g_engine) {
            LOGD("pauseEngine: %d", value);

            g_engine->pauseEngine(value);

#if 0
            if (value &&
                        g_engine->hasFeature(Engine::kSupportsSavingDuringRuntime) &&
                        g_engine->canSaveGameStateCurrently())
                  g_engine->saveGameState(0, "Android parachute");
#endif
      }

      pause = value;

      if (!pause) {
            // wake up all threads
            for (uint i = 0; i < 3; ++i)
                  sem_post(&pause_sem);
      }
}

#endif


Generated by  Doxygen 1.6.0   Back to index