Logo Search packages:      
Sourcecode: scummvm version File versions  Download package

android.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: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/tags/release-1-2-1/backends/platform/android/android.cpp $
 * $Id: android.cpp 53824 2010-10-25 13:30:46Z anguslees $
 *
 */

#include "backends/base-backend.h"
#include "base/main.h"
#include "graphics/surface.h"

#include "backends/platform/android/video.h"

#if defined(ANDROID_BACKEND)

#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 <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")))

// This replaces the bionic libc assert message with something that
// actually prints the assertion failure before aborting.
extern "C"
void __assert(const char *file, int line, const char *expr) {
      __android_log_assert(expr, LOG_TAG, "%s:%d: Assertion failure: %s",
                                     file, line, expr);
}

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;
      jmethodID MID_swapBuffers;

      int _screen_changeid;
      int _egl_surface_width;
      int _egl_surface_height;

      bool _force_redraw;

      // Game layer
      GLESPaletteTexture* _game_texture;
      int _shake_offset;
      Common::Rect _focus_rect;

      // 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 _enable_zoning;
      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;
      void enableZoning(bool enable) { _enable_zoning = enable; }
      void setSurfaceSize(int width, int height) {
            _egl_surface_width = width;
            _egl_surface_height = height;
      }

      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),
        _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),
        _enable_zoning(false),
        _savefile(0),
        _mixer(0),
        _timer(0),
        _fsFactory(new POSIXFilesystemFactory()),
        _asset_archive(new AndroidAssetArchive(am)),
        _shake_offset(0),
        _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");
      FIND_METHOD(swapBuffers, "()Z");

#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() {
      ENTER("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.

      GLESTexture::initGLExtensions();

      // 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();

      clearFocusRectangle();
      CHECK_GL_ERROR();
}

void OSystem_Android::destroyScummVMSurface() {
      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 ||
            (!_focus_rect.isEmpty() &&
             !Common::Rect(_game_texture->width(),
                                 _game_texture->height()).contains(_focus_rect))) {
            // These are the only cases 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);
      }

      if (_focus_rect.isEmpty()) {
            _game_texture->drawTexture(0, 0,
                                                   _egl_surface_width, _egl_surface_height);
      } else {
            glPushMatrix();
            glScalex(xdiv(_egl_surface_width, _focus_rect.width()),
                         xdiv(_egl_surface_height, _focus_rect.height()),
                         1 << 16);
            glTranslatex(-_focus_rect.left << 16, -_focus_rect.top << 16, 0);
            glScalex(xdiv(_game_texture->width(), _egl_surface_width),
                         xdiv(_game_texture->height(), _egl_surface_height),
                         1 << 16);
            _game_texture->drawTexture(0, 0,
                                                   _egl_surface_width, _egl_surface_height);
            glPopMatrix();
      }

      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();

      JNIEnv* env = JNU_GetEnv();
      if (!env->CallBooleanMethod(_back_ptr, MID_swapBuffers)) {
            // Context lost -> need to reinit GL
            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 (_enable_zoning) {
            _focus_rect = rect;
            _force_redraw = true;
      }
}

void OSystem_Android::clearFocusRectangle() {
      ENTER("clearFocusRectangle()");
      if (_enable_zoning) {
            _focus_rect = Common::Rect();
            _force_redraw = true;
      }
}

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);

      // Shouldn't need this, but works around a 'blank screen' bug on Nexus1
      updateScreen();
}

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]));

      // Shouldn't need this, but works around a 'blank screen' bug on Nexus1?
      updateScreen();
}

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

static void ScummVM_enableZoning(JNIEnv* env, jobject self, jboolean enable) {
      OSystem_Android* cpp_obj = OSystem_Android::fromJavaObject(env, self);
      cpp_obj->enableZoning(enable);
}

static void ScummVM_setSurfaceSize(JNIEnv* env, jobject self,
                                                   jint width, jint height) {
      OSystem_Android* cpp_obj = OSystem_Android::fromJavaObject(env, self);
      cpp_obj->setSurfaceSize(width, height);   
}

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 },
      { "enableZoning", "(Z)V",
        (void*)ScummVM_enableZoning },
      { "setSurfaceSize", "(II)V",
        (void*)ScummVM_setSurfaceSize },
};

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

Generated by  Doxygen 1.6.0   Back to index