aboutsummaryrefslogtreecommitdiff
path: root/backends/platform/android/android.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'backends/platform/android/android.cpp')
-rw-r--r--backends/platform/android/android.cpp1413
1 files changed, 1413 insertions, 0 deletions
diff --git a/backends/platform/android/android.cpp b/backends/platform/android/android.cpp
new file mode 100644
index 0000000000..76590ec823
--- /dev/null
+++ b/backends/platform/android/android.cpp
@@ -0,0 +1,1413 @@
+/* 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$
+ *
+ */
+
+#include "backends/base-backend.h"
+#include "base/main.h"
+#include "graphics/surface.h"
+
+#include "backends/platform/android/video.h"
+
+#if defined(ANDROID_BACKEND)
+
+#define ANDROID_VERSION_GE(major,minor) \
+ (ANDROID_MAJOR_VERSION > (major) || \
+ (ANDROID_MAJOR_VERSION == (major) && ANDROID_MINOR_VERSION >= (minor)))
+
+#include <jni.h>
+
+#include <string.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <sys/time.h>
+#include <time.h>
+
+#include <GLES/gl.h>
+#include <GLES/glext.h>
+#include <EGL/egl.h>
+#include <android/log.h>
+
+#include "common/archive.h"
+#include "common/util.h"
+#include "common/rect.h"
+#include "common/queue.h"
+#include "common/mutex.h"
+#include "common/events.h"
+#include "common/config-manager.h"
+
+#include "backends/fs/posix/posix-fs-factory.h"
+#include "backends/keymapper/keymapper.h"
+#include "backends/saves/default/default-saves.h"
+#include "backends/timer/default/default-timer.h"
+#include "backends/plugins/posix/posix-provider.h"
+#include "sound/mixer_intern.h"
+
+#include "backends/platform/android/asset-archive.h"
+
+#undef LOG_TAG
+#define LOG_TAG "ScummVM"
+
+#if 0
+#define ENTER(args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, args)
+#else
+#define ENTER(args...) /**/
+#endif
+
+// Fix JNIEXPORT declaration to actually do something useful
+#undef JNIEXPORT
+#define JNIEXPORT __attribute__ ((visibility("default")))
+
+static JavaVM *cached_jvm;
+static jfieldID FID_Event_type;
+static jfieldID FID_Event_synthetic;
+static jfieldID FID_Event_kbd_keycode;
+static jfieldID FID_Event_kbd_ascii;
+static jfieldID FID_Event_kbd_flags;
+static jfieldID FID_Event_mouse_x;
+static jfieldID FID_Event_mouse_y;
+static jfieldID FID_Event_mouse_relative;
+static jfieldID FID_ScummVM_nativeScummVM;
+static jmethodID MID_Object_wait;
+
+JNIEnv* JNU_GetEnv() {
+ JNIEnv* env;
+ bool version_unsupported =
+ cached_jvm->GetEnv((void**)&env, JNI_VERSION_1_2);
+ assert(! version_unsupported);
+ return env;
+}
+
+static void JNU_ThrowByName(JNIEnv* env, const char* name, const char* msg) {
+ jclass cls = env->FindClass(name);
+ // if cls is NULL, an exception has already been thrown
+ if (cls != NULL)
+ env->ThrowNew(cls, msg);
+ env->DeleteLocalRef(cls);
+}
+
+// floating point. use sparingly.
+template <class T>
+static inline T scalef(T in, float numerator, float denominator) {
+ return static_cast<float>(in) * numerator / denominator;
+}
+
+static inline GLfixed xdiv(int numerator, int denominator) {
+ assert(numerator < (1<<16));
+ return (numerator << 16) / denominator;
+}
+
+#ifdef DYNAMIC_MODULES
+class AndroidPluginProvider : public POSIXPluginProvider {
+protected:
+ virtual void addCustomDirectories(Common::FSList &dirs) const;
+};
+#endif
+
+
+#if 0
+#define CHECK_GL_ERROR() checkGlError(__FILE__, __LINE__)
+static const char* getGlErrStr(GLenum error) {
+ switch (error) {
+ case GL_NO_ERROR: return "GL_NO_ERROR";
+ case GL_INVALID_ENUM: return "GL_INVALID_ENUM";
+ case GL_INVALID_OPERATION: return "GL_INVALID_OPERATION";
+ case GL_STACK_OVERFLOW: return "GL_STACK_OVERFLOW";
+ case GL_STACK_UNDERFLOW: return "GL_STACK_UNDERFLOW";
+ case GL_OUT_OF_MEMORY: return "GL_OUT_OF_MEMORY";
+ }
+
+ static char buf[40];
+ snprintf(buf, sizeof(buf), "(Unknown GL error code 0x%x)", error);
+ return buf;
+}
+static void checkGlError(const char* file, int line) {
+ GLenum error = glGetError();
+ if (error != GL_NO_ERROR)
+ warning("%s:%d: GL error: %s", file, line, getGlErrStr(error));
+}
+#else
+#define CHECK_GL_ERROR() do {} while (false)
+#endif
+
+class OSystem_Android : public BaseBackend {
+private:
+ jobject _back_ptr; // back pointer to (java) peer instance
+ jmethodID MID_displayMessageOnOSD;
+ jmethodID MID_setWindowCaption;
+ jmethodID MID_initBackend;
+ jmethodID MID_audioSampleRate;
+ jmethodID MID_showVirtualKeyboard;
+ jmethodID MID_getSysArchives;
+ jmethodID MID_getPluginDirectories;
+ jmethodID MID_setupScummVMSurface;
+ jmethodID MID_destroyScummVMSurface;
+
+ int _screen_changeid;
+ EGLDisplay _egl_display;
+ EGLSurface _egl_surface;
+ EGLint _egl_surface_width;
+ EGLint _egl_surface_height;
+
+ bool _force_redraw;
+
+ // Game layer
+ GLESPaletteTexture* _game_texture;
+ int _shake_offset;
+ bool _full_screen_dirty;
+ Common::Array<Common::Rect> _dirty_rects;
+
+ // Overlay layer
+ GLES4444Texture* _overlay_texture;
+ bool _show_overlay;
+
+ // Mouse layer
+ GLESPaletteATexture* _mouse_texture;
+ Common::Point _mouse_hotspot;
+ int _mouse_targetscale;
+ bool _show_mouse;
+ bool _use_mouse_palette;
+
+ Common::Queue<Common::Event> _event_queue;
+ MutexRef _event_queue_lock;
+
+ bool _timer_thread_exit;
+ pthread_t _timer_thread;
+ static void* timerThreadFunc(void* arg);
+
+ bool _virtkeybd_on;
+
+ Common::SaveFileManager *_savefile;
+ Audio::MixerImpl *_mixer;
+ Common::TimerManager *_timer;
+ FilesystemFactory *_fsFactory;
+ Common::Archive *_asset_archive;
+ timeval _startTime;
+
+ void setupScummVMSurface();
+ void destroyScummVMSurface();
+ void setupKeymapper();
+ void _setCursorPalette(const byte *colors, uint start, uint num);
+
+public:
+ OSystem_Android(jobject am);
+ virtual ~OSystem_Android();
+ bool initJavaHooks(JNIEnv* env, jobject self);
+
+ static OSystem_Android* fromJavaObject(JNIEnv* env, jobject obj);
+ virtual void initBackend();
+ void addPluginDirectories(Common::FSList &dirs) const;
+
+ virtual bool hasFeature(Feature f);
+ virtual void setFeatureState(Feature f, bool enable);
+ virtual bool getFeatureState(Feature f);
+ virtual const GraphicsMode *getSupportedGraphicsModes() const;
+ virtual int getDefaultGraphicsMode() const;
+ bool setGraphicsMode(const char *name);
+ virtual bool setGraphicsMode(int mode);
+ virtual int getGraphicsMode() const;
+ virtual void initSize(uint width, uint height,
+ const Graphics::PixelFormat *format);
+ virtual int getScreenChangeID() const { return _screen_changeid; }
+ virtual int16 getHeight();
+ virtual int16 getWidth();
+ virtual void setPalette(const byte *colors, uint start, uint num);
+ virtual void grabPalette(byte *colors, uint start, uint num);
+ virtual void copyRectToScreen(const byte *buf, int pitch, int x, int y, int w, int h);
+ virtual void updateScreen();
+ virtual Graphics::Surface *lockScreen();
+ virtual void unlockScreen();
+ virtual void setShakePos(int shakeOffset);
+ virtual void fillScreen(uint32 col);
+ virtual void setFocusRectangle(const Common::Rect& rect);
+ virtual void clearFocusRectangle();
+
+ virtual void showOverlay();
+ virtual void hideOverlay();
+ virtual void clearOverlay();
+ virtual void grabOverlay(OverlayColor *buf, int pitch);
+ virtual void copyRectToOverlay(const OverlayColor *buf, int pitch, int x, int y, int w, int h);
+ virtual int16 getOverlayHeight();
+ virtual int16 getOverlayWidth();
+ virtual Graphics::PixelFormat getOverlayFormat() const {
+ // RGBA 4444
+ Graphics::PixelFormat format;
+ format.bytesPerPixel = 2;
+ format.rLoss = 8 - 4;
+ format.gLoss = 8 - 4;
+ format.bLoss = 8 - 4;
+ format.aLoss = 8 - 4;
+ format.rShift = 3*4;
+ format.gShift = 2*4;
+ format.bShift = 1*4;
+ format.aShift = 0*4;
+ return format;
+ }
+
+ virtual bool showMouse(bool visible);
+
+ virtual void warpMouse(int x, int y);
+ virtual void setMouseCursor(const byte *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, int cursorTargetScale, const Graphics::PixelFormat *format);
+ virtual void setCursorPalette(const byte *colors, uint start, uint num);
+ virtual void disableCursorPalette(bool disable);
+
+ virtual bool pollEvent(Common::Event &event);
+ void pushEvent(const Common::Event& event);
+ virtual uint32 getMillis();
+ virtual void delayMillis(uint msecs);
+
+ virtual MutexRef createMutex(void);
+ virtual void lockMutex(MutexRef mutex);
+ virtual void unlockMutex(MutexRef mutex);
+ virtual void deleteMutex(MutexRef mutex);
+
+ virtual void quit();
+
+ virtual void setWindowCaption(const char *caption);
+ virtual void displayMessageOnOSD(const char *msg);
+ virtual void showVirtualKeyboard(bool enable);
+
+ virtual Common::SaveFileManager *getSavefileManager();
+ virtual Audio::Mixer *getMixer();
+ virtual void getTimeAndDate(TimeDate &t) const;
+ virtual Common::TimerManager *getTimerManager();
+ virtual FilesystemFactory *getFilesystemFactory();
+ virtual void addSysArchivesToSearchSet(Common::SearchSet &s, int priority = 0);
+};
+
+OSystem_Android::OSystem_Android(jobject am)
+ : _back_ptr(0),
+ _egl_display(EGL_NO_DISPLAY),
+ _egl_surface(EGL_NO_SURFACE),
+ _screen_changeid(0),
+ _force_redraw(false),
+ _game_texture(NULL),
+ _overlay_texture(NULL),
+ _mouse_texture(NULL),
+ _use_mouse_palette(false),
+ _show_mouse(false),
+ _show_overlay(false),
+ _savefile(0),
+ _mixer(0),
+ _timer(0),
+ _fsFactory(new POSIXFilesystemFactory()),
+ _asset_archive(new AndroidAssetArchive(am)),
+ _shake_offset(0),
+ _full_screen_dirty(false),
+ _event_queue_lock(createMutex()) {
+}
+
+OSystem_Android::~OSystem_Android() {
+ ENTER("~OSystem_Android()");
+ delete _game_texture;
+ delete _overlay_texture;
+ delete _mouse_texture;
+ destroyScummVMSurface();
+ JNIEnv* env = JNU_GetEnv();
+ //env->DeleteWeakGlobalRef(_back_ptr);
+ env->DeleteGlobalRef(_back_ptr);
+ delete _savefile;
+ delete _mixer;
+ delete _timer;
+ delete _fsFactory;
+ delete _asset_archive;
+ deleteMutex(_event_queue_lock);
+}
+
+OSystem_Android* OSystem_Android::fromJavaObject(JNIEnv* env, jobject obj) {
+ jlong peer = env->GetLongField(obj, FID_ScummVM_nativeScummVM);
+ return (OSystem_Android*)peer;
+}
+
+bool OSystem_Android::initJavaHooks(JNIEnv* env, jobject self) {
+ // weak global ref to allow class to be unloaded
+ // ... except dalvik doesn't implement NewWeakGlobalRef (yet)
+ //_back_ptr = env->NewWeakGlobalRef(self);
+ _back_ptr = env->NewGlobalRef(self);
+
+ jclass cls = env->GetObjectClass(_back_ptr);
+
+#define FIND_METHOD(name, signature) do { \
+ MID_ ## name = env->GetMethodID(cls, #name, signature); \
+ if (MID_ ## name == NULL) \
+ return false; \
+ } while (0)
+
+ FIND_METHOD(setWindowCaption, "(Ljava/lang/String;)V");
+ FIND_METHOD(displayMessageOnOSD, "(Ljava/lang/String;)V");
+ FIND_METHOD(initBackend, "()V");
+ FIND_METHOD(audioSampleRate, "()I");
+ FIND_METHOD(showVirtualKeyboard, "(Z)V");
+ FIND_METHOD(getSysArchives, "()[Ljava/lang/String;");
+ FIND_METHOD(getPluginDirectories, "()[Ljava/lang/String;");
+ FIND_METHOD(setupScummVMSurface, "()V");
+ FIND_METHOD(destroyScummVMSurface, "()V");
+
+#undef FIND_METHOD
+
+ return true;
+}
+
+static void ScummVM_create(JNIEnv* env, jobject self, jobject am) {
+ OSystem_Android* cpp_obj = new OSystem_Android(am);
+ if (!cpp_obj->initJavaHooks(env, self))
+ // Exception already thrown by initJavaHooks
+ return;
+
+ env->SetLongField(self, FID_ScummVM_nativeScummVM, (jlong)cpp_obj);
+
+#ifdef DYNAMIC_MODULES
+ PluginManager::instance().addPluginProvider(new AndroidPluginProvider());
+#endif
+}
+
+static void ScummVM_nativeDestroy(JNIEnv* env, jobject self) {
+ OSystem_Android* cpp_obj = OSystem_Android::fromJavaObject(env, self);
+ delete cpp_obj;
+}
+
+static void ScummVM_audioMixCallback(JNIEnv* env, jobject self,
+ jbyteArray jbuf) {
+ OSystem_Android* cpp_obj = OSystem_Android::fromJavaObject(env, self);
+ jsize len = env->GetArrayLength(jbuf);
+ jbyte* buf = env->GetByteArrayElements(jbuf, NULL);
+ if (buf == NULL) {
+ warning("Unable to get Java audio byte array. Skipping.");
+ return;
+ }
+ Audio::MixerImpl* mixer =
+ static_cast<Audio::MixerImpl*>(cpp_obj->getMixer());
+ assert(mixer);
+ mixer->mixCallback(reinterpret_cast<byte*>(buf), len);
+ env->ReleaseByteArrayElements(jbuf, buf, 0);
+}
+
+static void ScummVM_setConfManInt(JNIEnv* env, jclass cls,
+ jstring key_obj, jint value) {
+ ENTER("setConfManInt(%p, %d)", key_obj, (int)value);
+ const char* key = env->GetStringUTFChars(key_obj, NULL);
+ if (key == NULL)
+ return;
+ ConfMan.setInt(key, value);
+ env->ReleaseStringUTFChars(key_obj, key);
+}
+
+static void ScummVM_setConfManString(JNIEnv* env, jclass cls, jstring key_obj,
+ jstring value_obj) {
+ ENTER("setConfManStr(%p, %p)", key_obj, value_obj);
+ const char* key = env->GetStringUTFChars(key_obj, NULL);
+ if (key == NULL)
+ return;
+ const char* value = env->GetStringUTFChars(value_obj, NULL);
+ if (value == NULL) {
+ env->ReleaseStringUTFChars(key_obj, key);
+ return;
+ }
+ ConfMan.set(key, value);
+ env->ReleaseStringUTFChars(value_obj, value);
+ env->ReleaseStringUTFChars(key_obj, key);
+}
+
+void* OSystem_Android::timerThreadFunc(void* arg) {
+ OSystem_Android* system = (OSystem_Android*)arg;
+ DefaultTimerManager* timer = (DefaultTimerManager*)(system->_timer);
+
+ struct timespec tv;
+ tv.tv_sec = 0;
+ tv.tv_nsec = 100 * 1000 * 1000; // 100ms
+
+ while (!system->_timer_thread_exit) {
+ timer->handler();
+ nanosleep(&tv, NULL);
+ }
+
+ return NULL;
+}
+
+void OSystem_Android::initBackend() {
+ ENTER("initBackend()");
+ JNIEnv* env = JNU_GetEnv();
+
+ ConfMan.setInt("autosave_period", 0);
+ ConfMan.setInt("FM_medium_quality", true);
+
+ // must happen before creating TimerManager to avoid race in
+ // creating EventManager
+ setupKeymapper();
+
+ // BUG: "transient" ConfMan settings get nuked by the options
+ // screen. Passing the savepath in this way makes it stick
+ // (via ConfMan.registerDefault)
+ _savefile = new DefaultSaveFileManager(ConfMan.get("savepath"));
+ _timer = new DefaultTimerManager();
+
+ gettimeofday(&_startTime, NULL);
+
+ jint sample_rate = env->CallIntMethod(_back_ptr, MID_audioSampleRate);
+ if (env->ExceptionCheck()) {
+ warning("Error finding audio sample rate - assuming 11025HZ");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ sample_rate = 11025;
+ }
+ _mixer = new Audio::MixerImpl(this, sample_rate);
+ _mixer->setReady(true);
+
+ env->CallVoidMethod(_back_ptr, MID_initBackend);
+ if (env->ExceptionCheck()) {
+ error("Error in Java initBackend");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ }
+
+ _timer_thread_exit = false;
+ pthread_create(&_timer_thread, NULL, timerThreadFunc, this);
+
+ OSystem::initBackend();
+
+ setupScummVMSurface();
+}
+
+void OSystem_Android::addPluginDirectories(Common::FSList &dirs) const {
+ ENTER("OSystem_Android::addPluginDirectories()");
+ JNIEnv* env = JNU_GetEnv();
+
+ jobjectArray array =
+ (jobjectArray)env->CallObjectMethod(_back_ptr, MID_getPluginDirectories);
+ if (env->ExceptionCheck()) {
+ warning("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 == NULL)
+ continue;
+ const char* path = env->GetStringUTFChars(path_obj, NULL);
+ if (path == NULL) {
+ warning("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 OSystem_Android::hasFeature(Feature f) {
+ return (f == kFeatureCursorHasPalette ||
+ f == kFeatureVirtualKeyboard ||
+ f == kFeatureOverlaySupportsAlpha);
+}
+
+void OSystem_Android::setFeatureState(Feature f, bool enable) {
+ ENTER("setFeatureState(%d, %d)", f, enable);
+ switch (f) {
+ case kFeatureVirtualKeyboard:
+ _virtkeybd_on = enable;
+ showVirtualKeyboard(enable);
+ break;
+ default:
+ break;
+ }
+}
+
+bool OSystem_Android::getFeatureState(Feature f) {
+ switch (f) {
+ case kFeatureVirtualKeyboard:
+ return _virtkeybd_on;
+ default:
+ return false;
+ }
+}
+
+const OSystem::GraphicsMode* OSystem_Android::getSupportedGraphicsModes() const {
+ static const OSystem::GraphicsMode s_supportedGraphicsModes[] = {
+ {"default", "Default", 1},
+ {0, 0, 0},
+ };
+ return s_supportedGraphicsModes;
+}
+
+
+int OSystem_Android::getDefaultGraphicsMode() const {
+ return 1;
+}
+
+bool OSystem_Android::setGraphicsMode(const char *mode) {
+ ENTER("setGraphicsMode(%s)", mode);
+ return true;
+}
+
+bool OSystem_Android::setGraphicsMode(int mode) {
+ ENTER("setGraphicsMode(%d)", mode);
+ return true;
+}
+
+int OSystem_Android::getGraphicsMode() const {
+ return 1;
+}
+
+void OSystem_Android::setupScummVMSurface() {
+ JNIEnv* env = JNU_GetEnv();
+ env->CallVoidMethod(_back_ptr, MID_setupScummVMSurface);
+ if (env->ExceptionCheck())
+ return;
+
+ // EGL set up with a new surface. Initialise OpenGLES context.
+
+ _egl_display = eglGetCurrentDisplay();
+ _egl_surface = eglGetCurrentSurface(EGL_DRAW);
+
+ static bool log_version = true;
+ if (log_version) {
+ __android_log_print(ANDROID_LOG_INFO, LOG_TAG,
+ "Using EGL %s (%s); GL %s/%s (%s)",
+ eglQueryString(_egl_display, EGL_VERSION),
+ eglQueryString(_egl_display, EGL_VENDOR),
+ glGetString(GL_VERSION),
+ glGetString(GL_RENDERER),
+ glGetString(GL_VENDOR));
+ log_version = false; // only log this once
+ }
+
+ GLESTexture::initGLExtensions();
+
+ if (!eglQuerySurface(_egl_display, _egl_surface,
+ EGL_WIDTH, &_egl_surface_width) ||
+ !eglQuerySurface(_egl_display, _egl_surface,
+ EGL_HEIGHT, &_egl_surface_height)) {
+ JNU_ThrowByName(env, "java/lang/RuntimeException",
+ "Error fetching EGL surface width/height");
+ return;
+ }
+ __android_log_print(ANDROID_LOG_INFO, LOG_TAG,
+ "New surface is %dx%d",
+ _egl_surface_width, _egl_surface_height);
+
+ CHECK_GL_ERROR();
+
+ // Turn off anything that looks like 3D ;)
+ glDisable(GL_CULL_FACE);
+ glDisable(GL_DEPTH_TEST);
+ glDisable(GL_LIGHTING);
+ glDisable(GL_FOG);
+ glDisable(GL_DITHER);
+ glShadeModel(GL_FLAT);
+ glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
+
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+ glEnableClientState(GL_VERTEX_ARRAY);
+ glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+
+ glEnable(GL_TEXTURE_2D);
+
+ if (!_game_texture)
+ _game_texture = new GLESPaletteTexture();
+ else
+ _game_texture->reinitGL();
+
+ if (!_overlay_texture)
+ _overlay_texture = new GLES4444Texture();
+ else
+ _overlay_texture->reinitGL();
+
+ if (!_mouse_texture)
+ _mouse_texture = new GLESPaletteATexture();
+ else
+ _mouse_texture->reinitGL();
+
+ glViewport(0, 0, _egl_surface_width, _egl_surface_height);
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glOrthof(0, _egl_surface_width, _egl_surface_height, 0, -1, 1);
+
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+
+ CHECK_GL_ERROR();
+
+ _force_redraw = true;
+}
+
+void OSystem_Android::destroyScummVMSurface() {
+ _egl_surface = EGL_NO_SURFACE;
+ JNIEnv* env = JNU_GetEnv();
+ env->CallVoidMethod(_back_ptr, MID_destroyScummVMSurface);
+ // Can't use OpenGLES functions after this
+}
+
+void OSystem_Android::initSize(uint width, uint height,
+ const Graphics::PixelFormat *format) {
+ ENTER("initSize(%d,%d,%p)", width, height, format);
+
+ _game_texture->allocBuffer(width, height);
+
+ // Cap at 320x200 or the ScummVM themes abort :/
+ GLuint overlay_width = MIN(_egl_surface_width, 320);
+ GLuint overlay_height = MIN(_egl_surface_height, 200);
+ _overlay_texture->allocBuffer(overlay_width, overlay_height);
+
+ // Don't know mouse size yet - it gets reallocated in
+ // setMouseCursor. We need the palette allocated before
+ // setMouseCursor however, so just take a guess at the desired
+ // size (it's small).
+ _mouse_texture->allocBuffer(20, 20);
+}
+
+int16 OSystem_Android::getHeight() {
+ return _game_texture->height();
+}
+
+int16 OSystem_Android::getWidth() {
+ return _game_texture->width();
+}
+
+void OSystem_Android::setPalette(const byte* colors, uint start, uint num) {
+ ENTER("setPalette(%p, %u, %u)", colors, start, num);
+
+ if (!_use_mouse_palette)
+ _setCursorPalette(colors, start, num);
+
+ byte* palette = _game_texture->palette() + start*3;
+ do {
+ for (int i = 0; i < 3; ++i)
+ palette[i] = colors[i];
+ palette += 3;
+ colors += 4;
+ } while (--num);
+}
+
+void OSystem_Android::grabPalette(byte *colors, uint start, uint num) {
+ ENTER("grabPalette(%p, %u, %u)", colors, start, num);
+ const byte* palette = _game_texture->palette_const() + start*3;
+ do {
+ for (int i = 0; i < 3; ++i)
+ colors[i] = palette[i];
+ colors[3] = 0xff; // alpha
+
+ palette += 3;
+ colors += 4;
+ } while (--num);
+}
+
+void OSystem_Android::copyRectToScreen(const byte *buf, int pitch,
+ int x, int y, int w, int h) {
+ ENTER("copyRectToScreen(%p, %d, %d, %d, %d, %d)",
+ buf, pitch, x, y, w, h);
+
+ _game_texture->updateBuffer(x, y, w, h, buf, pitch);
+}
+
+void OSystem_Android::updateScreen() {
+ //ENTER("updateScreen()");
+
+ if (!_force_redraw &&
+ !_game_texture->dirty() &&
+ !_overlay_texture->dirty() &&
+ !_mouse_texture->dirty())
+ return;
+
+ _force_redraw = false;
+
+ glPushMatrix();
+
+ if (_shake_offset != 0) {
+ // This is the only case where _game_texture doesn't
+ // cover the entire screen.
+ glClearColorx(0, 0, 0, 1 << 16);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ // Move everything up by _shake_offset (game) pixels
+ glTranslatex(0, -_shake_offset << 16, 0);
+ }
+
+ _game_texture->drawTexture(0, 0,
+ _egl_surface_width, _egl_surface_height);
+
+ CHECK_GL_ERROR();
+
+ if (_show_overlay) {
+ _overlay_texture->drawTexture(0, 0,
+ _egl_surface_width,
+ _egl_surface_height);
+ CHECK_GL_ERROR();
+ }
+
+ if (_show_mouse) {
+ glPushMatrix();
+
+ glTranslatex(-_mouse_hotspot.x << 16,
+ -_mouse_hotspot.y << 16,
+ 0);
+
+ // Scale up ScummVM -> OpenGL (pixel) coordinates
+ int texwidth, texheight;
+ if (_show_overlay) {
+ texwidth = getOverlayWidth();
+ texheight = getOverlayHeight();
+ } else {
+ texwidth = getWidth();
+ texheight = getHeight();
+ }
+ glScalex(xdiv(_egl_surface_width, texwidth),
+ xdiv(_egl_surface_height, texheight),
+ 1 << 16);
+
+ // Note the extra half texel to position the mouse in
+ // the middle of the x,y square:
+ const Common::Point& mouse = getEventManager()->getMousePos();
+ glTranslatex((mouse.x << 16) | 1 << 15,
+ (mouse.y << 16) | 1 << 15, 0);
+
+ // Mouse targetscale just seems to make the cursor way
+ // too big :/
+ //glScalex(_mouse_targetscale << 16, _mouse_targetscale << 16,
+ // 1 << 16);
+
+ _mouse_texture->drawTexture();
+
+ glPopMatrix();
+ }
+
+ glPopMatrix();
+
+ CHECK_GL_ERROR();
+
+ if (!eglSwapBuffers(_egl_display, _egl_surface)) {
+ EGLint error = eglGetError();
+ warning("eglSwapBuffers exited with error 0x%x", error);
+ // Some errors mean we need to reinit GL
+ if (error == EGL_CONTEXT_LOST) {
+ destroyScummVMSurface();
+ setupScummVMSurface();
+ }
+ }
+}
+
+Graphics::Surface *OSystem_Android::lockScreen() {
+ ENTER("lockScreen()");
+ Graphics::Surface* surface = _game_texture->surface();
+ assert(surface->pixels);
+ return surface;
+}
+
+void OSystem_Android::unlockScreen() {
+ ENTER("unlockScreen()");
+ assert(_game_texture->dirty());
+}
+
+void OSystem_Android::setShakePos(int shake_offset) {
+ ENTER("setShakePos(%d)", shake_offset);
+ if (_shake_offset != shake_offset) {
+ _shake_offset = shake_offset;
+ _force_redraw = true;
+ }
+}
+
+void OSystem_Android::fillScreen(uint32 col) {
+ ENTER("fillScreen(%u)", col);
+ assert(col < 256);
+ _game_texture->fillBuffer(col);
+}
+
+void OSystem_Android::setFocusRectangle(const Common::Rect& rect) {
+ ENTER("setFocusRectangle(%d,%d,%d,%d)",
+ rect.left, rect.top, rect.right, rect.bottom);
+#if 0
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glOrthof(rect.left, rect.right, rect.top, rect.bottom, 0, 1);
+ glMatrixMode(GL_MODELVIEW);
+
+ _force_redraw = true;
+#endif
+}
+
+void OSystem_Android::clearFocusRectangle() {
+ ENTER("clearFocusRectangle()");
+#if 0
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glOrthof(0, _egl_surface_width, _egl_surface_height, 0, -1, 1);
+ glMatrixMode(GL_MODELVIEW);
+
+ _force_redraw = true;
+#endif
+}
+
+void OSystem_Android::showOverlay() {
+ ENTER("showOverlay()");
+ _show_overlay = true;
+ _force_redraw = true;
+}
+
+void OSystem_Android::hideOverlay() {
+ ENTER("hideOverlay()");
+ _show_overlay = false;
+ _force_redraw = true;
+}
+
+void OSystem_Android::clearOverlay() {
+ ENTER("clearOverlay()");
+ _overlay_texture->fillBuffer(0);
+}
+
+void OSystem_Android::grabOverlay(OverlayColor *buf, int pitch) {
+ ENTER("grabOverlay(%p, %d)", buf, pitch);
+ // We support overlay alpha blending, so the pixel data here
+ // shouldn't actually be used. Let's fill it with zeros, I'm sure
+ // it will be fine...
+ const Graphics::Surface* surface = _overlay_texture->surface_const();
+ assert(surface->bytesPerPixel == sizeof(buf[0]));
+ int h = surface->h;
+ do {
+ memset(buf, 0, surface->w * sizeof(buf[0]));
+ buf += pitch; // This 'pitch' is pixels not bytes
+ } while (--h);
+}
+
+void OSystem_Android::copyRectToOverlay(const OverlayColor *buf, int pitch,
+ int x, int y, int w, int h) {
+ ENTER("copyRectToOverlay(%p, %d, %d, %d, %d, %d)",
+ buf, pitch, x, y, w, h);
+ const Graphics::Surface* surface = _overlay_texture->surface_const();
+ assert(surface->bytesPerPixel == sizeof(buf[0]));
+
+ // This 'pitch' is pixels not bytes
+ _overlay_texture->updateBuffer(x, y, w, h, buf, pitch * sizeof(buf[0]));
+}
+
+int16 OSystem_Android::getOverlayHeight() {
+ return _overlay_texture->height();
+}
+
+int16 OSystem_Android::getOverlayWidth() {
+ return _overlay_texture->width();
+}
+
+bool OSystem_Android::showMouse(bool visible) {
+ ENTER("showMouse(%d)", visible);
+ _show_mouse = visible;
+ return true;
+}
+
+void OSystem_Android::warpMouse(int x, int y) {
+ ENTER("warpMouse(%d, %d)", x, y);
+ // We use only the eventmanager's idea of the current mouse
+ // position, so there is nothing extra to do here.
+}
+
+void OSystem_Android::setMouseCursor(const byte *buf, uint w, uint h,
+ int hotspotX, int hotspotY,
+ uint32 keycolor, int cursorTargetScale,
+ const Graphics::PixelFormat *format) {
+ ENTER("setMouseCursor(%p, %u, %u, %d, %d, %d, %d, %p)",
+ buf, w, h, hotspotX, hotspotY, (int)keycolor, cursorTargetScale,
+ format);
+
+ assert(keycolor < 256);
+
+ _mouse_texture->allocBuffer(w, h);
+
+ // Update palette alpha based on keycolor
+ byte* palette = _mouse_texture->palette();
+ int i = 256;
+ do {
+ palette[3] = 0xff;
+ palette += 4;
+ } while (--i);
+ palette = _mouse_texture->palette();
+ palette[keycolor*4 + 3] = 0x00;
+ _mouse_texture->updateBuffer(0, 0, w, h, buf, w);
+
+ _mouse_hotspot = Common::Point(hotspotX, hotspotY);
+ _mouse_targetscale = cursorTargetScale;
+}
+
+void OSystem_Android::_setCursorPalette(const byte *colors,
+ uint start, uint num) {
+ byte* palette = _mouse_texture->palette() + start*4;
+ do {
+ for (int i = 0; i < 3; ++i)
+ palette[i] = colors[i];
+ // Leave alpha untouched to preserve keycolor
+
+ palette += 4;
+ colors += 4;
+ } while (--num);
+}
+
+void OSystem_Android::setCursorPalette(const byte *colors,
+ uint start, uint num) {
+ ENTER("setCursorPalette(%p, %u, %u)", colors, start, num);
+ _setCursorPalette(colors, start, num);
+ _use_mouse_palette = true;
+}
+
+void OSystem_Android::disableCursorPalette(bool disable) {
+ ENTER("disableCursorPalette(%d)", disable);
+ _use_mouse_palette = !disable;
+}
+
+void OSystem_Android::setupKeymapper() {
+#ifdef ENABLE_KEYMAPPER
+ using namespace Common;
+
+ Keymapper *mapper = getEventManager()->getKeymapper();
+
+ HardwareKeySet *keySet = new HardwareKeySet();
+ keySet->addHardwareKey(
+ new HardwareKey("n", KeyState(KEYCODE_n), "n (vk)",
+ kTriggerLeftKeyType,
+ kVirtualKeyboardActionType));
+ mapper->registerHardwareKeySet(keySet);
+
+ Keymap *globalMap = new Keymap("global");
+ Action *act;
+
+ act = new Action(globalMap, "VIRT", "Display keyboard",
+ kVirtualKeyboardActionType);
+ act->addKeyEvent(KeyState(KEYCODE_F7, ASCII_F7, 0));
+
+ mapper->addGlobalKeymap(globalMap);
+
+ mapper->pushKeymap("global");
+#endif
+}
+
+bool OSystem_Android::pollEvent(Common::Event &event) {
+ //ENTER("pollEvent()");
+ lockMutex(_event_queue_lock);
+ if (_event_queue.empty()) {
+ unlockMutex(_event_queue_lock);
+ return false;
+ }
+ event = _event_queue.pop();
+ unlockMutex(_event_queue_lock);
+
+ switch (event.type) {
+ case Common::EVENT_MOUSEMOVE:
+ // TODO: only dirty/redraw move bounds
+ _force_redraw = true;
+ // fallthrough
+ case Common::EVENT_LBUTTONDOWN:
+ case Common::EVENT_LBUTTONUP:
+ case Common::EVENT_RBUTTONDOWN:
+ case Common::EVENT_RBUTTONUP:
+ case Common::EVENT_WHEELUP:
+ case Common::EVENT_WHEELDOWN:
+ case Common::EVENT_MBUTTONDOWN:
+ case Common::EVENT_MBUTTONUP: {
+ if (event.kbd.flags == 1) { // relative mouse hack
+ // Relative (trackball) mouse hack.
+ const Common::Point& mouse_pos =
+ getEventManager()->getMousePos();
+ event.mouse.x += mouse_pos.x;
+ event.mouse.y += mouse_pos.y;
+ event.mouse.x = CLIP(event.mouse.x, (int16)0, _show_overlay ?
+ getOverlayWidth() : getWidth());
+ event.mouse.y = CLIP(event.mouse.y, (int16)0, _show_overlay ?
+ getOverlayHeight() : getHeight());
+ } else {
+ // Touchscreen events need to be converted
+ // from device to game coords first.
+ const GLESTexture* tex = _show_overlay
+ ? static_cast<GLESTexture*>(_overlay_texture)
+ : static_cast<GLESTexture*>(_game_texture);
+ event.mouse.x = scalef(event.mouse.x, tex->width(),
+ _egl_surface_width);
+ event.mouse.y = scalef(event.mouse.y, tex->height(),
+ _egl_surface_height);
+ event.mouse.x -= _shake_offset;
+ }
+ break;
+ }
+ case Common::EVENT_SCREEN_CHANGED:
+ debug("EVENT_SCREEN_CHANGED");
+ _screen_changeid++;
+ destroyScummVMSurface();
+ setupScummVMSurface();
+ break;
+ default:
+ break;
+ }
+
+ return true;
+}
+
+void OSystem_Android::pushEvent(const Common::Event& event) {
+ lockMutex(_event_queue_lock);
+
+ // Try to combine multiple queued mouse move events
+ if (event.type == Common::EVENT_MOUSEMOVE &&
+ !_event_queue.empty() &&
+ _event_queue.back().type == Common::EVENT_MOUSEMOVE) {
+ Common::Event tail = _event_queue.back();
+ if (event.kbd.flags) {
+ // relative movement hack
+ tail.mouse.x += event.mouse.x;
+ tail.mouse.y += event.mouse.y;
+ } else {
+ // absolute position
+ tail.kbd.flags = 0; // clear relative flag
+ tail.mouse.x = event.mouse.x;
+ tail.mouse.y = event.mouse.y;
+ }
+ }
+ else
+ _event_queue.push(event);
+
+ unlockMutex(_event_queue_lock);
+}
+
+static void ScummVM_pushEvent(JNIEnv* env, jobject self, jobject java_event) {
+ OSystem_Android* cpp_obj = OSystem_Android::fromJavaObject(env, self);
+
+ Common::Event event;
+ event.type = (Common::EventType)env->GetIntField(java_event,
+ FID_Event_type);
+ event.synthetic =
+ env->GetBooleanField(java_event, FID_Event_synthetic);
+
+ switch (event.type) {
+ case Common::EVENT_KEYDOWN:
+ case Common::EVENT_KEYUP:
+ event.kbd.keycode = (Common::KeyCode)env->GetIntField(
+ java_event, FID_Event_kbd_keycode);
+ event.kbd.ascii = static_cast<int>(env->GetIntField(
+ java_event, FID_Event_kbd_ascii));
+ event.kbd.flags = static_cast<int>(env->GetIntField(
+ java_event, FID_Event_kbd_flags));
+ break;
+ case Common::EVENT_MOUSEMOVE:
+ case Common::EVENT_LBUTTONDOWN:
+ case Common::EVENT_LBUTTONUP:
+ case Common::EVENT_RBUTTONDOWN:
+ case Common::EVENT_RBUTTONUP:
+ case Common::EVENT_WHEELUP:
+ case Common::EVENT_WHEELDOWN:
+ case Common::EVENT_MBUTTONDOWN:
+ case Common::EVENT_MBUTTONUP:
+ event.mouse.x =
+ env->GetIntField(java_event, FID_Event_mouse_x);
+ event.mouse.y =
+ env->GetIntField(java_event, FID_Event_mouse_y);
+ // This is a terrible hack. We stash "relativeness"
+ // in the kbd.flags field until pollEvent() can work
+ // it out.
+ event.kbd.flags = env->GetBooleanField(
+ java_event, FID_Event_mouse_relative) ? 1 : 0;
+ break;
+ default:
+ break;
+ }
+
+ cpp_obj->pushEvent(event);
+}
+
+uint32 OSystem_Android::getMillis() {
+ timeval curTime;
+ gettimeofday(&curTime, NULL);
+ return (uint32)(((curTime.tv_sec - _startTime.tv_sec) * 1000) + \
+ ((curTime.tv_usec - _startTime.tv_usec) / 1000));
+}
+
+void OSystem_Android::delayMillis(uint msecs) {
+ usleep(msecs * 1000);
+}
+
+OSystem::MutexRef OSystem_Android::createMutex() {
+ pthread_mutexattr_t attr;
+ pthread_mutexattr_init(&attr);
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
+
+ pthread_mutex_t *mutex = new pthread_mutex_t;
+ if (pthread_mutex_init(mutex, &attr) != 0) {
+ warning("pthread_mutex_init() failed!");
+ delete mutex;
+ return NULL;
+ }
+ return (MutexRef)mutex;
+}
+
+void OSystem_Android::lockMutex(MutexRef mutex) {
+ if (pthread_mutex_lock((pthread_mutex_t*)mutex) != 0)
+ warning("pthread_mutex_lock() failed!");
+}
+
+void OSystem_Android::unlockMutex(MutexRef mutex) {
+ if (pthread_mutex_unlock((pthread_mutex_t*)mutex) != 0)
+ warning("pthread_mutex_unlock() failed!");
+}
+
+void OSystem_Android::deleteMutex(MutexRef mutex) {
+ pthread_mutex_t* m = (pthread_mutex_t*)mutex;
+ if (pthread_mutex_destroy(m) != 0)
+ warning("pthread_mutex_destroy() failed!");
+ else
+ delete m;
+}
+
+void OSystem_Android::quit() {
+ ENTER("quit()");
+
+ _timer_thread_exit = true;
+ pthread_join(_timer_thread, NULL);
+}
+
+void OSystem_Android::setWindowCaption(const char *caption) {
+ ENTER("setWindowCaption(%s)", caption);
+ JNIEnv* env = JNU_GetEnv();
+ jstring java_caption = env->NewStringUTF(caption);
+ env->CallVoidMethod(_back_ptr, MID_setWindowCaption, java_caption);
+ if (env->ExceptionCheck()) {
+ warning("Failed to set window caption");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ }
+ env->DeleteLocalRef(java_caption);
+}
+
+void OSystem_Android::displayMessageOnOSD(const char *msg) {
+ ENTER("displayMessageOnOSD(%s)", msg);
+ JNIEnv* env = JNU_GetEnv();
+ jstring java_msg = env->NewStringUTF(msg);
+ env->CallVoidMethod(_back_ptr, MID_displayMessageOnOSD, java_msg);
+ if (env->ExceptionCheck()) {
+ warning("Failed to display OSD message");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ }
+ env->DeleteLocalRef(java_msg);
+}
+
+void OSystem_Android::showVirtualKeyboard(bool enable) {
+ ENTER("showVirtualKeyboard(%d)", enable);
+ JNIEnv* env = JNU_GetEnv();
+ env->CallVoidMethod(_back_ptr, MID_showVirtualKeyboard, enable);
+ if (env->ExceptionCheck()) {
+ error("Error trying to show virtual keyboard");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ }
+}
+
+Common::SaveFileManager *OSystem_Android::getSavefileManager() {
+ assert(_savefile);
+ return _savefile;
+}
+
+Audio::Mixer *OSystem_Android::getMixer() {
+ assert(_mixer);
+ return _mixer;
+}
+
+Common::TimerManager *OSystem_Android::getTimerManager() {
+ assert(_timer);
+ return _timer;
+}
+
+void OSystem_Android::getTimeAndDate(TimeDate &td) const {
+ struct tm tm;
+ const time_t curTime = time(NULL);
+ localtime_r(&curTime, &tm);
+ td.tm_sec = tm.tm_sec;
+ td.tm_min = tm.tm_min;
+ td.tm_hour = tm.tm_hour;
+ td.tm_mday = tm.tm_mday;
+ td.tm_mon = tm.tm_mon;
+ td.tm_year = tm.tm_year;
+}
+
+FilesystemFactory *OSystem_Android::getFilesystemFactory() {
+ return _fsFactory;
+}
+
+void OSystem_Android::addSysArchivesToSearchSet(Common::SearchSet &s,
+ int priority) {
+ s.add("ASSET", _asset_archive, priority, false);
+
+ JNIEnv* env = JNU_GetEnv();
+
+ jobjectArray array =
+ (jobjectArray)env->CallObjectMethod(_back_ptr, MID_getSysArchives);
+ if (env->ExceptionCheck()) {
+ warning("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, NULL);
+ if (path != NULL) {
+ s.addDirectory(path, path, priority);
+ env->ReleaseStringUTFChars(path_obj, path);
+ }
+ env->DeleteLocalRef(path_obj);
+ }
+}
+
+
+static jint ScummVM_scummVMMain(JNIEnv* env, jobject self, jobjectArray args) {
+ OSystem_Android* cpp_obj = OSystem_Android::fromJavaObject(env, self);
+
+ const int MAX_NARGS = 32;
+ int res = -1;
+
+ int argc = env->GetArrayLength(args);
+ if (argc > MAX_NARGS) {
+ JNU_ThrowByName(env, "java/lang/IllegalArgumentException",
+ "too many arguments");
+ return 0;
+ }
+
+ char* argv[MAX_NARGS];
+ int nargs; // note use in cleanup loop below
+ for (nargs = 0; nargs < argc; ++nargs) {
+ jstring arg = (jstring)env->GetObjectArrayElement(args, nargs);
+ if (arg == NULL) {
+ argv[nargs] = NULL;
+ } else {
+ const char* cstr = env->GetStringUTFChars(arg, NULL);
+ argv[nargs] = const_cast<char*>(cstr);
+ if (cstr == NULL)
+ goto cleanup; // exception already thrown
+ }
+ env->DeleteLocalRef(arg);
+ }
+
+ g_system = cpp_obj;
+ assert(g_system);
+ __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG,
+ "Entering scummvm_main with %d args", argc);
+ res = scummvm_main(argc, argv);
+ __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "Exiting scummvm_main");
+ g_system->quit();
+
+cleanup:
+ nargs--;
+ for (int i = 0; i < nargs; ++i) {
+ if (argv[i] == NULL)
+ continue;
+ jstring arg = (jstring)env->GetObjectArrayElement(args, nargs);
+ if (arg == NULL)
+ // Exception already thrown
+ return res;
+ env->ReleaseStringUTFChars(arg, argv[i]);
+ env->DeleteLocalRef(arg);
+ }
+
+ return res;
+}
+
+#ifdef DYNAMIC_MODULES
+void AndroidPluginProvider::addCustomDirectories(Common::FSList &dirs) const {
+ OSystem_Android* g_system_android = (OSystem_Android*)g_system;
+ g_system_android->addPluginDirectories(dirs);
+}
+#endif
+
+const static JNINativeMethod gMethods[] = {
+ { "create", "(Landroid/content/res/AssetManager;)V",
+ (void*)ScummVM_create },
+ { "nativeDestroy", "()V", (void*)ScummVM_nativeDestroy },
+ { "scummVMMain", "([Ljava/lang/String;)I",
+ (void*)ScummVM_scummVMMain },
+ { "pushEvent", "(Lorg/inodes/gus/scummvm/Event;)V",
+ (void*)ScummVM_pushEvent },
+ { "audioMixCallback", "([B)V",
+ (void*)ScummVM_audioMixCallback },
+ { "setConfMan", "(Ljava/lang/String;I)V",
+ (void*)ScummVM_setConfManInt },
+ { "setConfMan", "(Ljava/lang/String;Ljava/lang/String;)V",
+ (void*)ScummVM_setConfManString },
+};
+
+JNIEXPORT jint JNICALL
+JNI_OnLoad(JavaVM* jvm, void* reserved) {
+ cached_jvm = jvm;
+
+ JNIEnv* env;
+ if (jvm->GetEnv((void**)&env, JNI_VERSION_1_2))
+ return JNI_ERR;
+
+ jclass cls = env->FindClass("org/inodes/gus/scummvm/ScummVM");
+ if (cls == NULL)
+ return JNI_ERR;
+ if (env->RegisterNatives(cls, gMethods, ARRAYSIZE(gMethods)) < 0)
+ return JNI_ERR;
+
+ FID_ScummVM_nativeScummVM = env->GetFieldID(cls, "nativeScummVM", "J");
+ if (FID_ScummVM_nativeScummVM == NULL)
+ return JNI_ERR;
+
+ jclass event = env->FindClass("org/inodes/gus/scummvm/Event");
+ if (event == NULL)
+ return JNI_ERR;
+ FID_Event_type = env->GetFieldID(event, "type", "I");
+ if (FID_Event_type == NULL)
+ return JNI_ERR;
+ FID_Event_synthetic = env->GetFieldID(event, "synthetic", "Z");
+ if (FID_Event_synthetic == NULL)
+ return JNI_ERR;
+ FID_Event_kbd_keycode = env->GetFieldID(event, "kbd_keycode", "I");
+ if (FID_Event_kbd_keycode == NULL)
+ return JNI_ERR;
+ FID_Event_kbd_ascii = env->GetFieldID(event, "kbd_ascii", "I");
+ if (FID_Event_kbd_ascii == NULL)
+ return JNI_ERR;
+ FID_Event_kbd_flags = env->GetFieldID(event, "kbd_flags", "I");
+ if (FID_Event_kbd_flags == NULL)
+ return JNI_ERR;
+ FID_Event_mouse_x = env->GetFieldID(event, "mouse_x", "I");
+ if (FID_Event_mouse_x == NULL)
+ return JNI_ERR;
+ FID_Event_mouse_y = env->GetFieldID(event, "mouse_y", "I");
+ if (FID_Event_mouse_y == NULL)
+ return JNI_ERR;
+ FID_Event_mouse_relative = env->GetFieldID(event, "mouse_relative", "Z");
+ if (FID_Event_mouse_relative == NULL)
+ return JNI_ERR;
+
+ cls = env->FindClass("java/lang/Object");
+ if (cls == NULL)
+ return JNI_ERR;
+ MID_Object_wait = env->GetMethodID(cls, "wait", "()V");
+ if (MID_Object_wait == NULL)
+ return JNI_ERR;
+
+ return JNI_VERSION_1_2;
+}
+
+#endif