Merge pull request #613 from tobigun/android

ANDROID: preliminary Android port for ResidualVM
This commit is contained in:
Paweł Kołodziejski 2012-05-02 21:36:07 -07:00
commit 308f407969
39 changed files with 7116 additions and 7 deletions

View File

@ -0,0 +1,613 @@
/* 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.
*
*/
#if defined(__ANDROID__)
// Allow use of stuff in <time.h>
#define FORBIDDEN_SYMBOL_EXCEPTION_time_h
// Disable printf override in common/forbidden.h to avoid
// clashes with log.h from the Android SDK.
// That header file uses
// __attribute__ ((format(printf, 3, 4)))
// which gets messed up by our override mechanism; this could
// be avoided by either changing the Android SDK to use the equally
// legal and valid
// __attribute__ ((format(printf, 3, 4)))
// or by refining our printf override to use a varadic macro
// (which then wouldn't be portable, though).
// Anyway, for now we just disable the printf override globally
// for the Android port
#define FORBIDDEN_SYMBOL_EXCEPTION_printf
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/system_properties.h>
#include <time.h>
#include <unistd.h>
#include "common/util.h"
#include "common/textconsole.h"
#include "common/rect.h"
#include "common/queue.h"
#include "common/mutex.h"
#include "common/events.h"
#include "common/config-manager.h"
#include "backends/keymapper/keymapper.h"
#include "backends/saves/default/default-saves.h"
#include "backends/timer/default/default-timer.h"
#include "backends/platform/android/jni.h"
#include "backends/platform/android/android.h"
const char *android_log_tag = "ResidualVM";
// This replaces the bionic libc assert functions 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, android_log_tag,
"Assertion failure: '%s' in %s:%d",
expr, file, line);
}
void __assert2(const char *file, int line, const char *func,
const char *expr) {
__android_log_assert(expr, android_log_tag,
"Assertion failure: '%s' in %s:%d (%s)",
expr, file, line, func);
}
}
#ifdef ANDROID_DEBUG_GL
static const char *getGlErrStr(GLenum error) {
switch (error) {
case GL_INVALID_ENUM:
return "GL_INVALID_ENUM";
case GL_INVALID_VALUE:
return "GL_INVALID_VALUE";
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;
}
void checkGlError(const char *expr, const char *file, int line) {
GLenum error = glGetError();
if (error != GL_NO_ERROR)
LOGE("GL ERROR: %s on %s (%s:%d)", getGlErrStr(error), expr, file, line);
}
#endif
OSystem_Android::OSystem_Android(int audio_sample_rate, int audio_buffer_size) :
_audio_sample_rate(audio_sample_rate),
_audio_buffer_size(audio_buffer_size),
_screen_changeid(0),
_egl_surface_width(0),
_egl_surface_height(0),
_htc_fail(false),
_force_redraw(false),
_game_texture(0),
_game_pbuf(),
_overlay_texture(0),
_opengl(false),
_mouse_texture(0),
_mouse_texture_palette(0),
_mouse_texture_rgb(0),
_mouse_hotspot(),
_mouse_keycolor(0),
_use_mouse_palette(false),
_graphicsMode(0),
_fullscreen(true),
_ar_correction(true),
_show_mouse(false),
_show_overlay(false),
_virt_arrowkeys_pressed(false),
_enable_zoning(false),
_mixer(0),
_shake_offset(0),
_queuedEventTime(0),
_event_queue_lock(createMutex()),
_touch_pt_down(),
_touch_pt_scroll(),
_touch_pt_dt(),
_eventScaleX(100),
_eventScaleY(100),
// TODO put these values in some option dlg?
_touchpad_mode(true),
_touchpad_scale(66),
_dpad_scale(4),
_fingersDown(0),
_trackball_scale(2) {
_fsFactory = new POSIXFilesystemFactory();
Common::String mf = getSystemProperty("ro.product.manufacturer");
LOGI("Running on: [%s] [%s] [%s] [%s] [%s] SDK:%s ABI:%s",
mf.c_str(),
getSystemProperty("ro.product.model").c_str(),
getSystemProperty("ro.product.brand").c_str(),
getSystemProperty("ro.build.fingerprint").c_str(),
getSystemProperty("ro.build.display.id").c_str(),
getSystemProperty("ro.build.version.sdk").c_str(),
getSystemProperty("ro.product.cpu.abi").c_str());
mf.toLowercase();
_htc_fail = mf.contains("htc");
if (_htc_fail)
LOGI("Enabling HTC workaround");
}
OSystem_Android::~OSystem_Android() {
ENTER();
delete _mixer;
_mixer = 0;
delete _fsFactory;
_fsFactory = 0;
delete _timerManager;
_timerManager = 0;
deleteMutex(_event_queue_lock);
}
void *OSystem_Android::timerThreadFunc(void *arg) {
OSystem_Android *system = (OSystem_Android *)arg;
DefaultTimerManager *timer = (DefaultTimerManager *)(system->_timerManager);
// renice this thread to boost the audio thread
if (setpriority(PRIO_PROCESS, 0, 19) < 0)
LOGW("couldn't renice the timer thread");
JNI::attachThread();
struct timespec tv;
tv.tv_sec = 0;
tv.tv_nsec = 10 * 1000 * 1000; // 10ms
while (!system->_timer_thread_exit) {
if (JNI::pause) {
LOGD("timer thread going to sleep");
sem_wait(&JNI::pause_sem);
LOGD("timer thread woke up");
}
timer->handler();
nanosleep(&tv, 0);
}
JNI::detachThread();
return 0;
}
void *OSystem_Android::audioThreadFunc(void *arg) {
JNI::attachThread();
OSystem_Android *system = (OSystem_Android *)arg;
Audio::MixerImpl *mixer = system->_mixer;
uint buf_size = system->_audio_buffer_size;
JNIEnv *env = JNI::getEnv();
jbyteArray bufa = env->NewByteArray(buf_size);
bool paused = true;
byte *buf;
int offset, left, written;
int samples, i;
struct timespec tv_delay;
tv_delay.tv_sec = 0;
tv_delay.tv_nsec = 20 * 1000 * 1000;
uint msecs_full = buf_size * 1000 / (mixer->getOutputRate() * 2 * 2);
struct timespec tv_full;
tv_full.tv_sec = 0;
tv_full.tv_nsec = msecs_full * 1000 * 1000;
bool silence;
uint silence_count = 33;
while (!system->_audio_thread_exit) {
if (JNI::pause) {
JNI::setAudioStop();
paused = true;
silence_count = 33;
LOGD("audio thread going to sleep");
sem_wait(&JNI::pause_sem);
LOGD("audio thread woke up");
}
buf = (byte *)env->GetPrimitiveArrayCritical(bufa, 0);
assert(buf);
samples = mixer->mixCallback(buf, buf_size);
silence = samples < 1;
// looks stupid, and it is, but currently there's no way to detect
// silence-only buffers from the mixer
if (!silence) {
silence = true;
for (i = 0; i < samples; i += 2)
// SID streams constant crap
if (READ_UINT16(buf + i) > 32) {
silence = false;
break;
}
}
env->ReleasePrimitiveArrayCritical(bufa, buf, 0);
if (silence) {
if (!paused)
silence_count++;
// only pause after a while to prevent toggle mania
if (silence_count > 32) {
if (!paused) {
LOGD("AudioTrack pause");
JNI::setAudioPause();
paused = true;
}
nanosleep(&tv_full, 0);
continue;
}
}
if (paused) {
LOGD("AudioTrack play");
JNI::setAudioPlay();
paused = false;
silence_count = 0;
}
offset = 0;
left = buf_size;
written = 0;
while (left > 0) {
written = JNI::writeAudio(env, bufa, offset, left);
if (written < 0) {
LOGE("AudioTrack error: %d", written);
break;
}
// buffer full
if (written < left)
nanosleep(&tv_delay, 0);
offset += written;
left -= written;
}
if (written < 0)
break;
// prepare the next buffer, and run into the blocking AudioTrack.write
}
JNI::setAudioStop();
env->DeleteLocalRef(bufa);
JNI::detachThread();
return 0;
}
void OSystem_Android::initBackend() {
ENTER();
_main_thread = pthread_self();
ConfMan.registerDefault("fullscreen", true);
ConfMan.registerDefault("aspect_ratio", true);
ConfMan.setInt("autosave_period", 0);
ConfMan.setBool("FM_high_quality", false);
ConfMan.setBool("FM_medium_quality", true);
// TODO hackity hack
if (ConfMan.hasKey("multi_midi"))
_touchpad_mode = !ConfMan.getBool("multi_midi");
// 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)
_savefileManager = new DefaultSaveFileManager(ConfMan.get("savepath"));
_timerManager = new DefaultTimerManager();
gettimeofday(&_startTime, 0);
_mixer = new Audio::MixerImpl(this, _audio_sample_rate);
_mixer->setReady(true);
_timer_thread_exit = false;
pthread_create(&_timer_thread, 0, timerThreadFunc, this);
_audio_thread_exit = false;
pthread_create(&_audio_thread, 0, audioThreadFunc, this);
initSurface();
initViewport();
_game_texture = new GLESFakePalette565Texture();
_overlay_texture = new GLES4444Texture();
_mouse_texture_palette = new GLESFakePalette5551Texture();
_mouse_texture = _mouse_texture_palette;
initOverlay();
// renice this thread to boost the audio thread
if (setpriority(PRIO_PROCESS, 0, 19) < 0)
warning("couldn't renice the main thread");
JNI::setReadyForEvents(true);
EventsBaseBackend::initBackend();
}
void OSystem_Android::addPluginDirectories(Common::FSList &dirs) const {
ENTER();
JNI::getPluginDirectories(dirs);
}
bool OSystem_Android::hasFeature(Feature f) {
return (f == kFeatureFullscreenMode ||
f == kFeatureAspectRatioCorrection ||
f == kFeatureCursorPalette ||
f == kFeatureVirtualKeyboard ||
#ifdef USE_OPENGL
f == kFeatureOpenGL ||
#endif
f == kFeatureOverlaySupportsAlpha);
}
void OSystem_Android::setFeatureState(Feature f, bool enable) {
ENTER("%d, %d", f, enable);
switch (f) {
case kFeatureFullscreenMode:
_fullscreen = enable;
updateScreenRect();
break;
case kFeatureAspectRatioCorrection:
_ar_correction = enable;
updateScreenRect();
break;
case kFeatureVirtualKeyboard:
_virtkeybd_on = enable;
showVirtualKeyboard(enable);
break;
case kFeatureCursorPalette:
_use_mouse_palette = enable;
if (!enable)
disableCursorPalette();
break;
default:
break;
}
}
bool OSystem_Android::getFeatureState(Feature f) {
switch (f) {
case kFeatureFullscreenMode:
return _fullscreen;
case kFeatureAspectRatioCorrection:
return _ar_correction;
case kFeatureVirtualKeyboard:
return _virtkeybd_on;
case kFeatureCursorPalette:
return _use_mouse_palette;
default:
return false;
}
}
uint32 OSystem_Android::getMillis() {
timeval curTime;
gettimeofday(&curTime, 0);
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 0;
}
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();
JNI::setReadyForEvents(false);
_audio_thread_exit = true;
pthread_join(_audio_thread, 0);
_timer_thread_exit = true;
pthread_join(_timer_thread, 0);
delete _game_texture;
delete _overlay_texture;
delete _mouse_texture_palette;
delete _mouse_texture_rgb;
deinitSurface();
}
void OSystem_Android::setWindowCaption(const char *caption) {
ENTER("%s", caption);
JNI::setWindowCaption(caption);
}
void OSystem_Android::displayMessageOnOSD(const char *msg) {
ENTER("%s", msg);
JNI::displayMessageOnOSD(msg);
}
void OSystem_Android::showVirtualKeyboard(bool enable) {
ENTER("%d", enable);
JNI::showVirtualKeyboard(enable);
}
Audio::Mixer *OSystem_Android::getMixer() {
assert(_mixer);
return _mixer;
}
void OSystem_Android::getTimeAndDate(TimeDate &td) const {
struct tm tm;
const time_t curTime = time(0);
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;
}
void OSystem_Android::addSysArchivesToSearchSet(Common::SearchSet &s,
int priority) {
ENTER("");
JNI::addSysArchivesToSearchSet(s, priority);
}
void OSystem_Android::logMessage(LogMessageType::Type type,
const char *message) {
switch (type) {
case LogMessageType::kInfo:
__android_log_write(ANDROID_LOG_INFO, android_log_tag, message);
break;
case LogMessageType::kDebug:
__android_log_write(ANDROID_LOG_DEBUG, android_log_tag, message);
break;
case LogMessageType::kWarning:
__android_log_write(ANDROID_LOG_WARN, android_log_tag, message);
break;
case LogMessageType::kError:
__android_log_write(ANDROID_LOG_ERROR, android_log_tag, message);
break;
}
}
Common::String OSystem_Android::getSystemLanguage() const {
return Common::String::format("%s_%s",
getSystemProperty("persist.sys.language").c_str(),
getSystemProperty("persist.sys.country").c_str());
}
Common::String OSystem_Android::getSystemProperty(const char *name) const {
char value[PROP_VALUE_MAX];
int len = __system_property_get(name, value);
return Common::String(value, len);
}
#ifdef DYNAMIC_MODULES
void AndroidPluginProvider::addCustomDirectories(Common::FSList &dirs) const {
((OSystem_Android *)g_system)->addPluginDirectories(dirs);
}
#endif
#endif

View File

@ -0,0 +1,317 @@
/* 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.
*
*/
#ifndef _ANDROID_H_
#define _ANDROID_H_
#if defined(__ANDROID__)
#include "common/fs.h"
#include "common/archive.h"
#include "audio/mixer_intern.h"
#include "graphics/palette.h"
#include "graphics/surface.h"
#include "graphics/pixelbuffer.h"
#include "backends/base-backend.h"
#include "backends/plugins/posix/posix-provider.h"
#include "backends/fs/posix/posix-fs-factory.h"
#include "backends/platform/android/texture.h"
#include <pthread.h>
#include <android/log.h>
#include <GLES/gl.h>
#include <GLES/glext.h>
// toggles start
//#define ANDROID_DEBUG_ENTER
//#define ANDROID_DEBUG_GL
//#define ANDROID_DEBUG_GL_CALLS
// toggles end
extern const char *android_log_tag;
#define _ANDROID_LOG(prio, fmt, args...) __android_log_print(prio, android_log_tag, fmt, ## args)
#define LOGD(fmt, args...) _ANDROID_LOG(ANDROID_LOG_DEBUG, fmt, ##args)
#define LOGI(fmt, args...) _ANDROID_LOG(ANDROID_LOG_INFO, fmt, ##args)
#define LOGW(fmt, args...) _ANDROID_LOG(ANDROID_LOG_WARN, fmt, ##args)
#define LOGE(fmt, args...) _ANDROID_LOG(ANDROID_LOG_ERROR, fmt, ##args)
#ifdef ANDROID_DEBUG_ENTER
#define ENTER(fmt, args...) LOGD("%s(" fmt ")", __FUNCTION__, ##args)
#else
#define ENTER(fmt, args...) do { } while (false)
#endif
#ifdef ANDROID_DEBUG_GL
extern void checkGlError(const char *expr, const char *file, int line);
#ifdef ANDROID_DEBUG_GL_CALLS
#define GLCALLLOG(x, before) \
do { \
if (before) \
LOGD("calling '%s' (%s:%d)", x, __FILE__, __LINE__); \
else \
LOGD("returned from '%s' (%s:%d)", x, __FILE__, __LINE__); \
} while (false)
#else
#define GLCALLLOG(x, before) do { } while (false)
#endif
#define GLCALL(x) \
do { \
GLCALLLOG(#x, true); \
(x); \
GLCALLLOG(#x, false); \
checkGlError(#x, __FILE__, __LINE__); \
} while (false)
#define GLTHREADCHECK \
do { \
assert(pthread_self() == _main_thread); \
} while (false)
#else
#define GLCALL(x) do { (x); } while (false)
#define GLTHREADCHECK do { } while (false)
#endif
#ifdef DYNAMIC_MODULES
class AndroidPluginProvider : public POSIXPluginProvider {
protected:
virtual void addCustomDirectories(Common::FSList &dirs) const;
};
#endif
class OSystem_Android : public EventsBaseBackend, public PaletteManager {
private:
// passed from the dark side
int _audio_sample_rate;
int _audio_buffer_size;
int _screen_changeid;
int _egl_surface_width;
int _egl_surface_height;
bool _htc_fail;
bool _force_redraw;
bool _opengl;
// Game layer
GLESBaseTexture *_game_texture;
Graphics::PixelBuffer _game_pbuf;
int _shake_offset;
Common::Rect _focus_rect;
// Overlay layer
GLES4444Texture *_overlay_texture;
bool _show_overlay;
// Mouse layer
GLESBaseTexture *_mouse_texture;
GLESBaseTexture *_mouse_texture_palette;
GLES5551Texture *_mouse_texture_rgb;
Common::Point _mouse_hotspot;
uint32 _mouse_keycolor;
int _mouse_targetscale;
bool _show_mouse;
bool _use_mouse_palette;
int _virt_arrowkeys_pressed;
int _graphicsMode;
bool _fullscreen;
bool _ar_correction;
pthread_t _main_thread;
bool _timer_thread_exit;
pthread_t _timer_thread;
static void *timerThreadFunc(void *arg);
bool _audio_thread_exit;
pthread_t _audio_thread;
static void *audioThreadFunc(void *arg);
bool _enable_zoning;
bool _virtkeybd_on;
Audio::MixerImpl *_mixer;
timeval _startTime;
Common::String getSystemProperty(const char *name) const;
void initSurface();
void deinitSurface();
void initViewport();
void initOverlay();
#ifdef USE_RGB_COLOR
Common::String getPixelFormatName(const Graphics::PixelFormat &format) const;
void initTexture(GLESBaseTexture **texture, uint width, uint height,
const Graphics::PixelFormat *format);
#endif
void setupKeymapper();
void setCursorPaletteInternal(const byte *colors, uint start, uint num);
public:
OSystem_Android(int audio_sample_rate, int audio_buffer_size);
virtual ~OSystem_Android();
virtual void initBackend();
void addPluginDirectories(Common::FSList &dirs) const;
void enableZoning(bool enable) { _enable_zoning = enable; }
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;
virtual bool setGraphicsMode(int mode);
virtual int getGraphicsMode() const;
#ifdef USE_RGB_COLOR
virtual Graphics::PixelFormat getScreenFormat() const;
virtual Common::List<Graphics::PixelFormat> getSupportedFormats() const;
#endif
virtual void initSize(uint width, uint height,
const Graphics::PixelFormat *format);
enum FixupType {
kClear = 0, // glClear
kClearSwap, // glClear + swapBuffers
kClearUpdate // glClear + updateScreen
};
void clearScreen(FixupType type, byte count = 1);
void updateScreenRect();
virtual int getScreenChangeID() const;
virtual int16 getHeight();
virtual int16 getWidth();
virtual PaletteManager *getPaletteManager() {
return this;
}
public:
void pushEvent(int type, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6);
private:
Common::Queue<Common::Event> _event_queue;
Common::Event _queuedEvent;
uint32 _queuedEventTime;
MutexRef _event_queue_lock;
Common::Point _touch_pt_down, _touch_pt_scroll, _touch_pt_dt;
int _eventScaleX;
int _eventScaleY;
bool _touchpad_mode;
int _touchpad_scale;
int _trackball_scale;
int _dpad_scale;
int _fingersDown;
void clipMouse(Common::Point &p);
void scaleMouse(Common::Point &p, int x, int y, bool deductDrawRect = true);
void updateEventScale();
void disableCursorPalette();
void updateVirtArrowKeys(int keys);
int getTouchArea(int x, int y);
int checkVirtArrowKeys(int action, int x, int y);
void checkVirtArrowKeys(int pointer, int action, int x0, int y0, int x1, int y1);
protected:
// PaletteManager API
virtual void setPalette(const byte *colors, uint start, uint num);
virtual void grabPalette(byte *colors, uint start, uint num);
public:
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;
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 bool pollEvent(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 Audio::Mixer *getMixer();
virtual void getTimeAndDate(TimeDate &t) const;
virtual void logMessage(LogMessageType::Type type, const char *message);
virtual void addSysArchivesToSearchSet(Common::SearchSet &s,
int priority = 0);
virtual Common::String getSystemLanguage() const;
// ResidualVM specific method
virtual void launcherInitSize(uint w, uint h);
bool lockMouse(bool lock);
Graphics::PixelBuffer setupScreen(int screenW, int screenH, bool fullscreen, bool accel3d);
};
#endif
#endif

View File

@ -0,0 +1,202 @@
# Android specific build targets
# These must be incremented for each market upload
ANDROID_VERSIONCODE = 1
ANDROID_PLUGIN_VERSIONCODE = 1
JAVA_FILES = \
ResidualVM.java \
ResidualVMEvents.java \
ResidualVMApplication.java \
ResidualVMActivity.java \
EditableSurfaceView.java \
Unpacker.java
JAVA_FILES_PLUGIN = \
PluginProvider.java
JAVA_FILES_GEN = \
Manifest.java \
R.java
PATH_DIST = $(srcdir)/dists/android
PATH_RESOURCES = $(PATH_DIST)/res
PORT_DISTFILES = $(PATH_DIST)/README.Android
RESOURCES = \
$(PATH_RESOURCES)/values/strings.xml \
$(PATH_RESOURCES)/layout/main.xml \
$(PATH_RESOURCES)/layout/splash.xml \
$(PATH_RESOURCES)/drawable/gradient.xml \
$(PATH_RESOURCES)/drawable/residualvm.png \
$(PATH_RESOURCES)/drawable/residualvm_big.png
PLUGIN_RESOURCES = \
$(PATH_RESOURCES)/values/strings.xml \
$(PATH_RESOURCES)/drawable/residualvm.png
# FIXME: find/mark plugin entry points and add all this back again:
#LDFLAGS += -Wl,--gc-sections
#CXXFLAGS += -ffunction-sections -fdata-sections -fvisibility=hidden -fvisibility-inlines-hidden
AAPT = $(ANDROID_SDK)/platform-tools/aapt
ADB = $(ANDROID_SDK)/platform-tools/adb
DX = $(ANDROID_SDK)/platform-tools/dx
APKBUILDER = $(ANDROID_SDK)/tools/apkbuilder
JAVAC ?= javac
JAVACFLAGS = -source 1.5 -target 1.5
# This is a bit silly. I want to compile against the 2.1 android.jar,
# to make the compiler check that I don't use something that requires
# a newer Android. However, in order to use android:installLocation,
# we need to give aapt a version >=8 android.jar - even though the
# result will work ok on 2.0+.
ANDROID_JAR = $(ANDROID_SDK)/platforms/android-7/android.jar
ANDROID_JAR8 = $(ANDROID_SDK)/platforms/android-8/android.jar
PATH_BUILD = build.tmp
PATH_BUILD_ASSETS = $(PATH_BUILD)/assets
PATH_BUILD_CLASSES_MAIN_TOP = $(PATH_BUILD)/classes.main
PATH_BUILD_CLASSES_PLUGIN_TOP = $(PATH_BUILD)/classes.plugin
PATH_STAGE_PREFIX = build.stage
PATH_STAGE_MAIN = $(PATH_STAGE_PREFIX).main
PATH_REL = org/residualvm/residualvm
PATH_SRC_TOP = $(srcdir)/backends/platform/android
PATH_SRC = $(PATH_SRC_TOP)/$(PATH_REL)
PATH_GEN_TOP = $(PATH_BUILD)/java
PATH_GEN = $(PATH_GEN_TOP)/$(PATH_REL)
PATH_CLASSES_MAIN = $(PATH_BUILD_CLASSES_MAIN_TOP)/$(PATH_REL)
PATH_CLASSES_PLUGIN = $(PATH_BUILD_CLASSES_PLUGIN_TOP)/$(PATH_REL)
FILE_MANIFEST_SRC = $(srcdir)/dists/android/AndroidManifest.xml
FILE_MANIFEST = $(PATH_BUILD)/AndroidManifest.xml
FILE_DEX = $(PATH_BUILD)/classes.dex
FILE_DEX_PLUGIN = $(PATH_BUILD)/plugins/classes.dex
FILE_RESOURCES = resources.ap_
FILE_RESOURCES_MAIN = $(PATH_BUILD)/$(FILE_RESOURCES)
SRC_GEN = $(addprefix $(PATH_GEN)/, $(JAVA_FILES_GEN))
CLASSES_MAIN = $(addprefix $(PATH_CLASSES_MAIN)/, $(JAVA_FILES:%.java=%.class))
CLASSES_GEN = $(addprefix $(PATH_CLASSES_MAIN)/, $(JAVA_FILES_GEN:%.java=%.class))
CLASSES_PLUGIN = $(addprefix $(PATH_CLASSES_PLUGIN)/, $(JAVA_FILES_PLUGIN:%.java=%.class))
APK_MAIN = residualvm.apk
APK_PLUGINS = $(patsubst plugins/lib%.so, residualvm-engine-%.apk, $(PLUGINS))
$(FILE_MANIFEST): $(FILE_MANIFEST_SRC)
@$(MKDIR) -p $(@D)
sed "s/@ANDROID_VERSIONCODE@/$(ANDROID_VERSIONCODE)/" < $< > $@
$(SRC_GEN): $(FILE_MANIFEST) $(filter %.xml,$(RESOURCES)) $(ANDROID_JAR8)
@$(MKDIR) -p $(PATH_GEN_TOP)
$(AAPT) package -m -J $(PATH_GEN_TOP) -M $< -S $(PATH_RESOURCES) -I $(ANDROID_JAR8)
$(PATH_CLASSES_MAIN)/%.class: $(PATH_GEN)/%.java $(SRC_GEN)
@$(MKDIR) -p $(@D)
$(JAVAC) $(JAVACFLAGS) -cp $(PATH_SRC_TOP) -d $(PATH_BUILD_CLASSES_MAIN_TOP) -bootclasspath $(ANDROID_JAR) $<
$(PATH_CLASSES_MAIN)/%.class: $(PATH_SRC)/%.java $(SRC_GEN)
@$(MKDIR) -p $(@D)
$(JAVAC) $(JAVACFLAGS) -cp $(PATH_SRC_TOP):$(PATH_GEN_TOP) -d $(PATH_BUILD_CLASSES_MAIN_TOP) -bootclasspath $(ANDROID_JAR) $<
$(PATH_CLASSES_PLUGIN)/%.class: $(PATH_SRC)/%.java
@$(MKDIR) -p $(@D)
$(JAVAC) $(JAVACFLAGS) -cp $(PATH_SRC_TOP) -d $(PATH_BUILD_CLASSES_PLUGIN_TOP) -bootclasspath $(ANDROID_JAR) $<
$(FILE_DEX): $(CLASSES_MAIN) $(CLASSES_GEN)
$(DX) --dex --output=$@ $(PATH_BUILD_CLASSES_MAIN_TOP)
$(FILE_DEX_PLUGIN): $(CLASSES_PLUGIN)
@$(MKDIR) -p $(@D)
$(DX) --dex --output=$@ $(PATH_BUILD_CLASSES_PLUGIN_TOP)
$(PATH_BUILD)/%/AndroidManifest.xml: $(PATH_DIST)/mkplugin.sh $(srcdir)/configure $(PATH_DIST)/plugin-manifest.xml
@$(MKDIR) -p $(@D)
$(PATH_DIST)/mkplugin.sh $(srcdir)/configure $* $(PATH_DIST)/plugin-manifest.xml $(ANDROID_PLUGIN_VERSIONCODE) $@
$(PATH_STAGE_PREFIX).%/res/values/strings.xml: $(PATH_DIST)/mkplugin.sh $(srcdir)/configure $(PATH_DIST)/plugin-manifest.xml
@$(MKDIR) -p $(@D)
$(PATH_DIST)/mkplugin.sh $(srcdir)/configure $* $(PATH_DIST)/plugin-strings.xml $(ANDROID_PLUGIN_VERSIONCODE) $@
$(PATH_STAGE_PREFIX).%/res/drawable/residualvm.png: $(PATH_RESOURCES)/drawable/residualvm.png
@$(MKDIR) -p $(@D)
$(CP) $< $@
$(FILE_RESOURCES_MAIN): $(FILE_MANIFEST) $(RESOURCES) $(ANDROID_JAR8) $(DIST_FILES_THEMES) $(DIST_FILES_ENGINEDATA)
$(INSTALL) -d $(PATH_BUILD_ASSETS)
$(INSTALL) -c -m 644 $(DIST_FILES_THEMES) $(DIST_FILES_ENGINEDATA) $(PATH_BUILD_ASSETS)/
work_dir=`pwd`; \
for i in $(PATH_BUILD_ASSETS)/*.zip; do \
echo "recompress $$i"; \
cd $$work_dir; \
$(RM) -rf $(PATH_BUILD_ASSETS)/tmp; \
$(MKDIR) $(PATH_BUILD_ASSETS)/tmp; \
unzip -q $$i -d $(PATH_BUILD_ASSETS)/tmp; \
cd $(PATH_BUILD_ASSETS)/tmp; \
zip -r ../`basename $$i` *; \
done
@$(RM) -rf $(PATH_BUILD_ASSETS)/tmp
$(AAPT) package -f -0 zip -M $< -S $(PATH_RESOURCES) -A $(PATH_BUILD_ASSETS) -I $(ANDROID_JAR8) -F $@
$(PATH_BUILD)/%/$(FILE_RESOURCES): $(PATH_BUILD)/%/AndroidManifest.xml $(PATH_STAGE_PREFIX).%/res/values/strings.xml $(PATH_STAGE_PREFIX).%/res/drawable/residualvm.png plugins/lib%.so $(ANDROID_JAR8)
$(AAPT) package -f -M $< -S $(PATH_STAGE_PREFIX).$*/res -I $(ANDROID_JAR8) -F $@
# Package installer won't delete old libresidualvm.so on upgrade so
# replace it with a zero size file
$(APK_MAIN): $(EXECUTABLE) $(FILE_RESOURCES_MAIN) $(FILE_DEX)
$(INSTALL) -d $(PATH_STAGE_MAIN)/common/lib/armeabi
touch $(PATH_STAGE_MAIN)/common/lib/armeabi/libresidualvm.so
$(INSTALL) -d $(PATH_STAGE_MAIN)/common/mylib/armeabi
$(INSTALL) -c -m 644 libresidualvm.so $(PATH_STAGE_MAIN)/common/mylib/armeabi/
$(STRIP) $(PATH_STAGE_MAIN)/common/mylib/armeabi/libresidualvm.so
$(APKBUILDER) $@ -z $(FILE_RESOURCES_MAIN) -f $(FILE_DEX) -rf $(PATH_STAGE_MAIN)/common || { $(RM) $@; exit 1; }
residualvm-engine-%.apk: plugins/lib%.so $(PATH_BUILD)/%/$(FILE_RESOURCES) $(FILE_DEX_PLUGIN)
$(INSTALL) -d $(PATH_STAGE_PREFIX).$*/apk/mylib/armeabi/
$(INSTALL) -c -m 644 plugins/lib$*.so $(PATH_STAGE_PREFIX).$*/apk/mylib/armeabi/
$(STRIP) $(PATH_STAGE_PREFIX).$*/apk/mylib/armeabi/lib$*.so
$(APKBUILDER) $@ -z $(PATH_BUILD)/$*/$(FILE_RESOURCES) -f $(FILE_DEX_PLUGIN) -rf $(PATH_STAGE_PREFIX).$*/apk || { $(RM) $@; exit 1; }
all: $(APK_MAIN) $(APK_PLUGINS)
clean: androidclean
androidclean:
@$(RM) -rf $(PATH_BUILD) $(PATH_STAGE_PREFIX).* *.apk release
# remove debugging signature
release/%.apk: %.apk
@$(MKDIR) -p $(@D)
@$(RM) $@
$(CP) $< $@.tmp
zip -d $@.tmp META-INF/\*
jarsigner $(JARSIGNER_FLAGS) $@.tmp release
zipalign 4 $@.tmp $@
$(RM) $@.tmp
androidrelease: $(addprefix release/, $(APK_MAIN) $(APK_PLUGINS))
androidtestmain: $(APK_MAIN)
$(ADB) install -r $(APK_MAIN)
$(ADB) shell am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -n org.residualvm.residualvm/.Unpacker
androidtest: $(APK_MAIN) $(APK_PLUGINS)
@set -e; for apk in $^; do \
$(ADB) install -r $$apk; \
done
$(ADB) shell am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -n org.residualvm.residualvm/.Unpacker
# used by buildbot!
androiddistdebug: all
$(MKDIR) debug
$(CP) $(APK_MAIN) $(APK_PLUGINS) debug/
for i in $(DIST_FILES_DOCS) $(PORT_DISTFILES); do \
sed 's/$$/\r/' < $$i > debug/`basename $$i`.txt; \
done
.PHONY: androidrelease androidtest

View File

@ -0,0 +1,504 @@
/* 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.
*
*/
#if defined(__ANDROID__)
#include <jni.h>
#include <sys/types.h>
#include <unistd.h>
#include "common/str.h"
#include "common/stream.h"
#include "common/util.h"
#include "common/archive.h"
#include "common/debug.h"
#include "common/textconsole.h"
#include "backends/platform/android/jni.h"
#include "backends/platform/android/asset-archive.h"
// Must match android.content.res.AssetManager.ACCESS_*
const jint ACCESS_UNKNOWN = 0;
const jint ACCESS_RANDOM = 1;
// This might be useful to someone else. Assumes markSupported() == true.
class JavaInputStream : public Common::SeekableReadStream {
public:
JavaInputStream(JNIEnv *env, jobject is);
virtual ~JavaInputStream();
virtual bool eos() const {
return _eos;
}
virtual bool err() const {
return _err;
}
virtual void clearErr() {
_eos = _err = false;
}
virtual uint32 read(void *dataPtr, uint32 dataSize);
virtual int32 pos() const {
return _pos;
}
virtual int32 size() const {
return _len;
}
virtual bool seek(int32 offset, int whence = SEEK_SET);
private:
void close(JNIEnv *env);
jmethodID MID_mark;
jmethodID MID_available;
jmethodID MID_close;
jmethodID MID_read;
jmethodID MID_reset;
jmethodID MID_skip;
jobject _input_stream;
jsize _buflen;
jbyteArray _buf;
uint32 _pos;
jint _len;
bool _eos;
bool _err;
};
JavaInputStream::JavaInputStream(JNIEnv *env, jobject is) :
_eos(false),
_err(false),
_pos(0)
{
_input_stream = env->NewGlobalRef(is);
_buflen = 8192;
_buf = (jbyteArray)env->NewGlobalRef(env->NewByteArray(_buflen));
jclass cls = env->GetObjectClass(_input_stream);
MID_mark = env->GetMethodID(cls, "mark", "(I)V");
assert(MID_mark);
MID_available = env->GetMethodID(cls, "available", "()I");
assert(MID_available);
MID_close = env->GetMethodID(cls, "close", "()V");
assert(MID_close);
MID_read = env->GetMethodID(cls, "read", "([BII)I");
assert(MID_read);
MID_reset = env->GetMethodID(cls, "reset", "()V");
assert(MID_reset);
MID_skip = env->GetMethodID(cls, "skip", "(J)J");
assert(MID_skip);
// Mark start of stream, so we can reset back to it.
// readlimit is set to something bigger than anything we might
// want to seek within.
env->CallVoidMethod(_input_stream, MID_mark, 10 * 1024 * 1024);
_len = env->CallIntMethod(_input_stream, MID_available);
}
JavaInputStream::~JavaInputStream() {
JNIEnv *env = JNI::getEnv();
close(env);
env->DeleteGlobalRef(_buf);
env->DeleteGlobalRef(_input_stream);
}
void JavaInputStream::close(JNIEnv *env) {
env->CallVoidMethod(_input_stream, MID_close);
if (env->ExceptionCheck())
env->ExceptionClear();
}
uint32 JavaInputStream::read(void *dataPtr, uint32 dataSize) {
JNIEnv *env = JNI::getEnv();
if (_buflen < jint(dataSize)) {
_buflen = dataSize;
env->DeleteGlobalRef(_buf);
_buf = static_cast<jbyteArray>(env->NewGlobalRef(env->NewByteArray(_buflen)));
}
jint ret = env->CallIntMethod(_input_stream, MID_read, _buf, 0, dataSize);
if (env->ExceptionCheck()) {
warning("Exception during JavaInputStream::read(%p, %d)",
dataPtr, dataSize);
env->ExceptionDescribe();
env->ExceptionClear();
_err = true;
ret = -1;
} else if (ret == -1) {
_eos = true;
ret = 0;
} else {
env->GetByteArrayRegion(_buf, 0, ret, static_cast<jbyte *>(dataPtr));
_pos += ret;
}
return ret;
}
bool JavaInputStream::seek(int32 offset, int whence) {
JNIEnv *env = JNI::getEnv();
uint32 newpos;
switch (whence) {
case SEEK_SET:
newpos = offset;
break;
case SEEK_CUR:
newpos = _pos + offset;
break;
case SEEK_END:
newpos = _len + offset;
break;
default:
debug("Unknown 'whence' arg %d", whence);
return false;
}
jlong skip_bytes;
if (newpos > _pos) {
skip_bytes = newpos - _pos;
} else {
// Can't skip backwards, so jump back to start and skip from there.
env->CallVoidMethod(_input_stream, MID_reset);
if (env->ExceptionCheck()) {
warning("Failed to rewind to start of asset stream");
env->ExceptionDescribe();
env->ExceptionClear();
return false;
}
_pos = 0;
skip_bytes = newpos;
}
while (skip_bytes > 0) {
jlong ret = env->CallLongMethod(_input_stream, MID_skip, skip_bytes);
if (env->ExceptionCheck()) {
warning("Failed to skip %ld bytes into asset stream",
static_cast<long>(skip_bytes));
env->ExceptionDescribe();
env->ExceptionClear();
return false;
} else if (ret == 0) {
warning("InputStream->skip(%ld) didn't skip any bytes. Aborting seek.",
static_cast<long>(skip_bytes));
// No point looping forever...
return false;
}
_pos += ret;
skip_bytes -= ret;
}
_eos = false;
return true;
}
// Must match android.content.res.AssetFileDescriptor.UNKNOWN_LENGTH
const jlong UNKNOWN_LENGTH = -1;
// Reading directly from a fd is so much more efficient, that it is
// worth optimising for.
class AssetFdReadStream : public Common::SeekableReadStream {
public:
AssetFdReadStream(JNIEnv *env, jobject assetfd);
virtual ~AssetFdReadStream();
virtual bool eos() const {
return _eos;
}
virtual bool err() const {
return _err;
}
virtual void clearErr() {
_eos = _err = false;
}
virtual uint32 read(void *dataPtr, uint32 dataSize);
virtual int32 pos() const {
return _pos;
}
virtual int32 size() const {
return _declared_len;
}
virtual bool seek(int32 offset, int whence = SEEK_SET);
private:
void close(JNIEnv *env);
int _fd;
jmethodID MID_close;
jobject _assetfd;
jlong _start_off;
jlong _declared_len;
uint32 _pos;
bool _eos;
bool _err;
};
AssetFdReadStream::AssetFdReadStream(JNIEnv *env, jobject assetfd) :
_eos(false),
_err(false),
_pos(0)
{
_assetfd = env->NewGlobalRef(assetfd);
jclass cls = env->GetObjectClass(_assetfd);
MID_close = env->GetMethodID(cls, "close", "()V");
assert(MID_close);
jmethodID MID_getStartOffset =
env->GetMethodID(cls, "getStartOffset", "()J");
assert(MID_getStartOffset);
_start_off = env->CallLongMethod(_assetfd, MID_getStartOffset);
jmethodID MID_getDeclaredLength =
env->GetMethodID(cls, "getDeclaredLength", "()J");
assert(MID_getDeclaredLength);
_declared_len = env->CallLongMethod(_assetfd, MID_getDeclaredLength);
jmethodID MID_getFileDescriptor =
env->GetMethodID(cls, "getFileDescriptor",
"()Ljava/io/FileDescriptor;");
assert(MID_getFileDescriptor);
jobject javafd = env->CallObjectMethod(_assetfd, MID_getFileDescriptor);
assert(javafd);
jclass fd_cls = env->GetObjectClass(javafd);
jfieldID FID_descriptor = env->GetFieldID(fd_cls, "descriptor", "I");
assert(FID_descriptor);
_fd = env->GetIntField(javafd, FID_descriptor);
}
AssetFdReadStream::~AssetFdReadStream() {
JNIEnv *env = JNI::getEnv();
env->CallVoidMethod(_assetfd, MID_close);
if (env->ExceptionCheck())
env->ExceptionClear();
env->DeleteGlobalRef(_assetfd);
}
uint32 AssetFdReadStream::read(void *dataPtr, uint32 dataSize) {
if (_declared_len != UNKNOWN_LENGTH) {
jlong cap = _declared_len - _pos;
if (dataSize > cap)
dataSize = cap;
}
int ret = ::read(_fd, dataPtr, dataSize);
if (ret == 0)
_eos = true;
else if (ret == -1)
_err = true;
else
_pos += ret;
return ret;
}
bool AssetFdReadStream::seek(int32 offset, int whence) {
if (whence == SEEK_SET) {
if (_declared_len != UNKNOWN_LENGTH && offset > _declared_len)
offset = _declared_len;
offset += _start_off;
} else if (whence == SEEK_END && _declared_len != UNKNOWN_LENGTH) {
whence = SEEK_SET;
offset = _start_off + _declared_len + offset;
}
int ret = lseek(_fd, offset, whence);
if (ret == -1)
return false;
_pos = ret - _start_off;
_eos = false;
return true;
}
AndroidAssetArchive::AndroidAssetArchive(jobject am) {
JNIEnv *env = JNI::getEnv();
_am = env->NewGlobalRef(am);
jclass cls = env->GetObjectClass(_am);
MID_open = env->GetMethodID(cls, "open",
"(Ljava/lang/String;I)Ljava/io/InputStream;");
assert(MID_open);
MID_openFd = env->GetMethodID(cls, "openFd", "(Ljava/lang/String;)"
"Landroid/content/res/AssetFileDescriptor;");
assert(MID_openFd);
MID_list = env->GetMethodID(cls, "list",
"(Ljava/lang/String;)[Ljava/lang/String;");
assert(MID_list);
}
AndroidAssetArchive::~AndroidAssetArchive() {
JNIEnv *env = JNI::getEnv();
env->DeleteGlobalRef(_am);
}
bool AndroidAssetArchive::hasFile(const Common::String &name) const {
JNIEnv *env = JNI::getEnv();
jstring path = env->NewStringUTF(name.c_str());
jobject result = env->CallObjectMethod(_am, MID_open, path, ACCESS_UNKNOWN);
if (env->ExceptionCheck()) {
// Assume FileNotFoundException
//warning("Error while calling AssetManager->open(%s)", name.c_str());
//env->ExceptionDescribe();
env->ExceptionClear();
env->DeleteLocalRef(path);
return false;
}
env->DeleteLocalRef(result);
env->DeleteLocalRef(path);
return true;
}
int AndroidAssetArchive::listMembers(Common::ArchiveMemberList &member_list) const {
JNIEnv *env = JNI::getEnv();
Common::List<Common::String> dirlist;
dirlist.push_back("");
int count = 0;
while (!dirlist.empty()) {
const Common::String dir = dirlist.back();
dirlist.pop_back();
jstring jpath = env->NewStringUTF(dir.c_str());
jobjectArray jpathlist =
(jobjectArray)env->CallObjectMethod(_am, MID_list, jpath);
if (env->ExceptionCheck()) {
warning("Error while calling AssetManager->list(%s). Ignoring.",
dir.c_str());
env->ExceptionDescribe();
env->ExceptionClear();
// May as well keep going ...
continue;
}
env->DeleteLocalRef(jpath);
for (jsize i = 0; i < env->GetArrayLength(jpathlist); ++i) {
jstring elem = (jstring)env->GetObjectArrayElement(jpathlist, i);
const char *p = env->GetStringUTFChars(elem, 0);
if (strlen(p)) {
Common::String thispath = dir;
if (!thispath.empty())
thispath += "/";
thispath += p;
// Assume files have a . in them, and directories don't
if (strchr(p, '.')) {
member_list.push_back(getMember(thispath));
++count;
} else {
dirlist.push_back(thispath);
}
}
env->ReleaseStringUTFChars(elem, p);
env->DeleteLocalRef(elem);
}
env->DeleteLocalRef(jpathlist);
}
return count;
}
const Common::ArchiveMemberPtr AndroidAssetArchive::getMember(const Common::String &name) const {
return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(name, this));
}
Common::SeekableReadStream *AndroidAssetArchive::createReadStreamForMember(const Common::String &path) const {
JNIEnv *env = JNI::getEnv();
jstring jpath = env->NewStringUTF(path.c_str());
// Try openFd() first ...
jobject afd = env->CallObjectMethod(_am, MID_openFd, jpath);
if (env->ExceptionCheck())
env->ExceptionClear();
else if (afd != 0) {
// success :)
env->DeleteLocalRef(jpath);
return new AssetFdReadStream(env, afd);
}
// ... and fallback to normal open() if that doesn't work
jobject is = env->CallObjectMethod(_am, MID_open, jpath, ACCESS_RANDOM);
if (env->ExceptionCheck()) {
// Assume FileNotFoundException
//warning("Error opening %s", path.c_str());
//env->ExceptionDescribe();
env->ExceptionClear();
env->DeleteLocalRef(jpath);
return 0;
}
return new JavaInputStream(env, is);
}
#endif

View File

@ -0,0 +1,54 @@
/* 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.
*
*/
#ifndef _ANDROID_ASSET_H_
#define _ANDROID_ASSET_H_
#if defined(__ANDROID__)
#include <jni.h>
#include "common/str.h"
#include "common/stream.h"
#include "common/util.h"
#include "common/archive.h"
class AndroidAssetArchive : public Common::Archive {
public:
AndroidAssetArchive(jobject am);
virtual ~AndroidAssetArchive();
virtual bool hasFile(const Common::String &name) const;
virtual int listMembers(Common::ArchiveMemberList &list) const;
virtual const Common::ArchiveMemberPtr getMember(const Common::String &name) const;
virtual Common::SeekableReadStream *createReadStreamForMember(const Common::String &name) const;
private:
jmethodID MID_open;
jmethodID MID_openFd;
jmethodID MID_list;
jobject _am;
};
#endif
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,886 @@
/* 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.
*
*/
#if defined(__ANDROID__)
// Allow use of stuff in <time.h>
#define FORBIDDEN_SYMBOL_EXCEPTION_time_h
// Disable printf override in common/forbidden.h to avoid
// clashes with log.h from the Android SDK.
// That header file uses
// __attribute__ ((format(printf, 3, 4)))
// which gets messed up by our override mechanism; this could
// be avoided by either changing the Android SDK to use the equally
// legal and valid
// __attribute__ ((format(printf, 3, 4)))
// or by refining our printf override to use a varadic macro
// (which then wouldn't be portable, though).
// Anyway, for now we just disable the printf override globally
// for the Android port
#define FORBIDDEN_SYMBOL_EXCEPTION_printf
#include "common/endian.h"
#include "graphics/conversion.h"
#include "backends/platform/android/android.h"
#include "backends/platform/android/jni.h"
static inline GLfixed xdiv(int numerator, int denominator) {
assert(numerator < (1 << 16));
return (numerator << 16) / denominator;
}
// ResidualVM specific method
void OSystem_Android::launcherInitSize(uint w, uint h) {
//setupScreen(w, h, true, false);
}
// ResidualVM specific method
bool OSystem_Android::lockMouse(bool lock) {
_show_mouse = lock;
return true;
}
const OSystem::GraphicsMode *OSystem_Android::getSupportedGraphicsModes() const {
static const OSystem::GraphicsMode s_supportedGraphicsModes[] = {
{ "default", "Default", 0 },
{ "filter", "Linear filtering", 1 },
{ 0, 0, 0 },
};
return s_supportedGraphicsModes;
}
int OSystem_Android::getDefaultGraphicsMode() const {
return 0;
}
bool OSystem_Android::setGraphicsMode(int mode) {
ENTER("%d", mode);
if (_game_texture)
_game_texture->setLinearFilter(mode == 1);
if (_overlay_texture)
_overlay_texture->setLinearFilter(mode == 1);
if (_mouse_texture)
_mouse_texture->setLinearFilter(mode == 1);
_graphicsMode = mode;
return true;
}
int OSystem_Android::getGraphicsMode() const {
return _graphicsMode;
}
#ifdef USE_RGB_COLOR
Graphics::PixelFormat OSystem_Android::getScreenFormat() const {
return _game_texture->getPixelFormat();
}
Common::List<Graphics::PixelFormat> OSystem_Android::getSupportedFormats() const {
Common::List<Graphics::PixelFormat> res;
res.push_back(GLES565Texture::pixelFormat());
res.push_back(GLES5551Texture::pixelFormat());
res.push_back(GLES4444Texture::pixelFormat());
res.push_back(Graphics::PixelFormat::createFormatCLUT8());
return res;
}
Common::String OSystem_Android::getPixelFormatName(const Graphics::PixelFormat &format) const {
if (format.bytesPerPixel == 1)
return "CLUT8";
if (format.aLoss == 8)
return Common::String::format("RGB%u%u%u",
8 - format.rLoss,
8 - format.gLoss,
8 - format.bLoss);
return Common::String::format("RGBA%u%u%u%u",
8 - format.rLoss,
8 - format.gLoss,
8 - format.bLoss,
8 - format.aLoss);
}
void OSystem_Android::initTexture(GLESBaseTexture **texture,
uint width, uint height,
const Graphics::PixelFormat *format) {
assert(texture);
Graphics::PixelFormat format_clut8 =
Graphics::PixelFormat::createFormatCLUT8();
Graphics::PixelFormat format_current;
Graphics::PixelFormat format_new;
if (*texture)
format_current = (*texture)->getPixelFormat();
else
format_current = Graphics::PixelFormat();
if (format)
format_new = *format;
else
format_new = format_clut8;
if (format_current != format_new) {
if (*texture)
LOGD("switching pixel format from: %s",
getPixelFormatName((*texture)->getPixelFormat()).c_str());
delete *texture;
if (format_new == GLES565Texture::pixelFormat())
*texture = new GLES565Texture();
else if (format_new == GLES5551Texture::pixelFormat())
*texture = new GLES5551Texture();
else if (format_new == GLES4444Texture::pixelFormat())
*texture = new GLES4444Texture();
else {
// TODO what now?
if (format_new != format_clut8)
LOGE("unsupported pixel format: %s",
getPixelFormatName(format_new).c_str());
*texture = new GLESFakePalette565Texture;
}
LOGD("new pixel format: %s",
getPixelFormatName((*texture)->getPixelFormat()).c_str());
}
(*texture)->allocBuffer(width, height);
}
#endif
void OSystem_Android::initSurface() {
LOGD("initializing surface");
assert(!JNI::haveSurface());
_screen_changeid = JNI::surface_changeid;
_egl_surface_width = JNI::egl_surface_width;
_egl_surface_height = JNI::egl_surface_height;
assert(_egl_surface_width > 0 && _egl_surface_height > 0);
JNI::initSurface();
// Initialize OpenGLES context.
GLESTexture::initGLExtensions();
if (_game_texture)
_game_texture->reinit();
if (_overlay_texture) {
_overlay_texture->reinit();
initOverlay();
}
if (_mouse_texture)
_mouse_texture->reinit();
}
void OSystem_Android::deinitSurface() {
if (!JNI::haveSurface())
return;
LOGD("deinitializing surface");
_screen_changeid = JNI::surface_changeid;
_egl_surface_width = 0;
_egl_surface_height = 0;
// release texture resources
if (_game_texture)
_game_texture->release();
if (_overlay_texture)
_overlay_texture->release();
if (_mouse_texture)
_mouse_texture->release();
JNI::deinitSurface();
}
void OSystem_Android::initViewport() {
LOGD("initializing viewport");
assert(JNI::haveSurface());
if (_opengl) {
GLCALL(glEnable(GL_DITHER));
GLCALL(glShadeModel(GL_SMOOTH));
GLCALL(glDisableClientState(GL_VERTEX_ARRAY));
GLCALL(glDisableClientState(GL_TEXTURE_COORD_ARRAY));
GLCALL(glDisable(GL_TEXTURE_2D));
GLCALL(glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST));
} else {
// Turn off anything that looks like 3D ;)
GLCALL(glDisable(GL_DITHER));
GLCALL(glShadeModel(GL_FLAT));
GLCALL(glEnableClientState(GL_VERTEX_ARRAY));
GLCALL(glEnableClientState(GL_TEXTURE_COORD_ARRAY));
GLCALL(glEnable(GL_TEXTURE_2D));
GLCALL(glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST));
}
GLCALL(glDisable(GL_CULL_FACE));
GLCALL(glDisable(GL_DEPTH_TEST));
GLCALL(glDisable(GL_LIGHTING));
GLCALL(glDisable(GL_FOG));
GLCALL(glEnable(GL_BLEND));
GLCALL(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
GLCALL(glViewport(0, 0, _egl_surface_width, _egl_surface_height));
GLCALL(glMatrixMode(GL_PROJECTION));
GLCALL(glLoadIdentity());
GLCALL(glOrthof(0, _egl_surface_width, _egl_surface_height, 0, -1, 1));
GLCALL(glMatrixMode(GL_MODELVIEW));
GLCALL(glLoadIdentity());
clearFocusRectangle();
}
void OSystem_Android::initOverlay() {
// minimum of 320x200
// (surface can get smaller when opening the virtual keyboard on *QVGA*)
int overlay_width = MAX(_egl_surface_width, 320);
int overlay_height = MAX(_egl_surface_height, 200);
// the 'normal' theme layout uses a max height of 400 pixels. if the
// surface is too big we use only a quarter of the size so that the widgets
// don't get too small. if the surface height has less than 800 pixels, this
// enforces the 'lowres' layout, which will be scaled back up by factor 2x,
// but this looks way better than the 'normal' layout scaled by some
// calculated factors
while (overlay_height > 480) {
overlay_width /= 2;
overlay_height /= 2;
}
LOGI("overlay size is %ux%u", overlay_width, overlay_height);
_overlay_texture->allocBuffer(overlay_width, overlay_height);
_overlay_texture->setDrawRect(0, 0,
_egl_surface_width, _egl_surface_height);
}
void OSystem_Android::initSize(uint width, uint height,
const Graphics::PixelFormat *format) {
ENTER("%d, %d, %p", width, height, format);
GLTHREADCHECK;
#ifdef USE_RGB_COLOR
initTexture(&_game_texture, width, height, format);
#else
_game_texture->allocBuffer(width, height);
#endif
updateScreenRect();
updateEventScale();
// 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_palette->allocBuffer(20, 20);
clearScreen(kClear);
}
void OSystem_Android::clearScreen(FixupType type, byte count) {
assert(count > 0);
bool sm = _show_mouse;
_show_mouse = false;
GLCALL(glDisable(GL_SCISSOR_TEST));
for (byte i = 0; i < count; ++i) {
// clear screen
GLCALL(glClearColorx(0, 0, 0, 1 << 16));
if (_opengl) {
GLCALL(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT));
} else {
GLCALL(glClear(GL_COLOR_BUFFER_BIT));
}
switch (type) {
case kClear:
break;
case kClearSwap:
JNI::swapBuffers();
break;
case kClearUpdate:
_force_redraw = true;
updateScreen();
break;
}
}
if (!_show_overlay)
GLCALL(glEnable(GL_SCISSOR_TEST));
_show_mouse = sm;
_force_redraw = true;
}
void OSystem_Android::updateScreenRect() {
Common::Rect rect(0, 0, _egl_surface_width, _egl_surface_height);
_overlay_texture->setDrawRect(rect);
uint16 w = _game_texture->width();
uint16 h = _game_texture->height();
if (w && h && !_fullscreen) {
if (_ar_correction && w == 320 && h == 200)
h = 240;
float dpi[2];
JNI::getDPI(dpi);
float screen_ar;
if (dpi[0] != 0.0 && dpi[1] != 0.0) {
// horizontal orientation
screen_ar = (dpi[1] * _egl_surface_width) /
(dpi[0] * _egl_surface_height);
} else {
screen_ar = float(_egl_surface_width) / float(_egl_surface_height);
}
float game_ar = float(w) / float(h);
if (screen_ar > game_ar) {
rect.setWidth(round(_egl_surface_height * game_ar));
rect.moveTo((_egl_surface_width - rect.width()) / 2, 0);
} else {
rect.setHeight(round(_egl_surface_width / game_ar));
rect.moveTo((_egl_surface_height - rect.height()) / 2, 0);
}
}
glScissor(rect.left, rect.top, rect.width(), rect.height());
_game_texture->setDrawRect(rect);
}
int OSystem_Android::getScreenChangeID() const {
return _screen_changeid;
}
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("%p, %u, %u", colors, start, num);
#ifdef USE_RGB_COLOR
assert(_game_texture->hasPalette());
#endif
GLTHREADCHECK;
if (!_use_mouse_palette)
setCursorPaletteInternal(colors, start, num);
const Graphics::PixelFormat &pf = _game_texture->getPalettePixelFormat();
byte *p = _game_texture->palette() + start * 2;
for (uint i = 0; i < num; ++i, colors += 3, p += 2)
WRITE_UINT16(p, pf.RGBToColor(colors[0], colors[1], colors[2]));
}
void OSystem_Android::grabPalette(byte *colors, uint start, uint num) {
ENTER("%p, %u, %u", colors, start, num);
#ifdef USE_RGB_COLOR
assert(_game_texture->hasPalette());
#endif
GLTHREADCHECK;
const Graphics::PixelFormat &pf = _game_texture->getPalettePixelFormat();
const byte *p = _game_texture->palette_const() + start * 2;
for (uint i = 0; i < num; ++i, colors += 3, p += 2)
pf.colorToRGB(READ_UINT16(p), colors[0], colors[1], colors[2]);
}
void OSystem_Android::copyRectToScreen(const byte *buf, int pitch,
int x, int y, int w, int h) {
ENTER("%p, %d, %d, %d, %d, %d", buf, pitch, x, y, w, h);
GLTHREADCHECK;
_game_texture->updateBuffer(x, y, w, h, buf, pitch);
}
// ResidualVM specific method
Graphics::PixelBuffer OSystem_Android::setupScreen(int screenW, int screenH, bool fullscreen, bool accel3d) {
_opengl = accel3d;
initViewport();
if (_opengl) {
// resize game texture
initSize(screenW, screenH, 0);
// format is not used by the gfx_opengl driver, use fake format
_game_pbuf.set(Graphics::PixelFormat(), 0);
} else {
Graphics::PixelFormat format = GLES565Texture::pixelFormat();
initSize(screenW, screenH, &format);
// as there is no support for the texture surface's lock/unlock mechanism in gfx_tinygl/...
// do not use _game_texture->surface()->pixels directly
_game_pbuf.create(_game_texture->getPixelFormat(),
_game_texture->width() * _game_texture->height(), DisposeAfterUse::YES);
}
return _game_pbuf;
}
void OSystem_Android::updateScreen() {
//ENTER();
GLTHREADCHECK;
if (!JNI::haveSurface())
return;
if (!_opengl) {
if (_game_pbuf) {
int pitch = _game_texture->width() * _game_texture->getPixelFormat().bytesPerPixel;
_game_texture->updateBuffer(0, 0, _game_texture->width(), _game_texture->height(),
_game_pbuf.getRawBuffer(), pitch);
}
if (!_force_redraw &&
!_game_texture->dirty() &&
!_overlay_texture->dirty() &&
!_mouse_texture->dirty())
return;
_force_redraw = false;
// clear pointer leftovers in dead areas
// also, HTC's GLES drivers are made of fail and don't preserve the buffer
// ( http://www.khronos.org/registry/egl/specs/EGLTechNote0001.html )
if ((_show_overlay || _htc_fail) && !_fullscreen)
clearScreen(kClear);
GLCALL(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.
clearScreen(kClear);
// Move everything up by _shake_offset (game) pixels
GLCALL(glTranslatex(0, -_shake_offset << 16, 0));
}
// TODO this doesnt work on those sucky drivers, do it differently
// if (_show_overlay)
// GLCALL(glColor4ub(0x9f, 0x9f, 0x9f, 0x9f));
if (_focus_rect.isEmpty()) {
_game_texture->drawTextureRect();
} else {
GLCALL(glPushMatrix());
GLCALL(glScalex(xdiv(_egl_surface_width, _focus_rect.width()),
xdiv(_egl_surface_height, _focus_rect.height()),
1 << 16));
GLCALL(glTranslatex(-_focus_rect.left << 16,
-_focus_rect.top << 16, 0));
GLCALL(glScalex(xdiv(_game_texture->width(), _egl_surface_width),
xdiv(_game_texture->height(), _egl_surface_height),
1 << 16));
_game_texture->drawTextureRect();
GLCALL(glPopMatrix());
}
int cs = _mouse_targetscale;
if (_show_overlay) {
// TODO see above
// GLCALL(glColor4ub(0xff, 0xff, 0xff, 0xff));
// ugly, but the modern theme sets a wacko factor, only god knows why
cs = 1;
GLCALL(_overlay_texture->drawTextureRect());
}
if (_show_mouse && !_mouse_texture->isEmpty()) {
GLCALL(glPushMatrix());
const Common::Point &mouse = getEventManager()->getMousePos();
// Scale up ResidualVM -> OpenGL (pixel) coordinates
if (_show_overlay) {
GLCALL(glScalex(xdiv(_egl_surface_width,
_overlay_texture->width()),
xdiv(_egl_surface_height,
_overlay_texture->height()),
1 << 16));
} else {
const Common::Rect &r = _game_texture->getDrawRect();
GLCALL(glTranslatex(r.left << 16,
r.top << 16,
0));
GLCALL(glScalex(xdiv(r.width(), _game_texture->width()),
xdiv(r.height(), _game_texture->height()),
1 << 16));
}
GLCALL(glTranslatex((-_mouse_hotspot.x * cs) << 16,
(-_mouse_hotspot.y * cs) << 16,
0));
// Note the extra half texel to position the mouse in
// the middle of the x,y square:
GLCALL(glTranslatex((mouse.x << 16) | 1 << 15,
(mouse.y << 16) | 1 << 15, 0));
GLCALL(glScalex(cs << 16, cs << 16, 1 << 16));
_mouse_texture->drawTextureOrigin();
GLCALL(glPopMatrix());
}
GLCALL(glPopMatrix());
}
if (!JNI::swapBuffers())
LOGW("swapBuffers failed: 0x%x", glGetError());
}
Graphics::Surface *OSystem_Android::lockScreen() {
ENTER();
GLTHREADCHECK;
Graphics::Surface *surface = _game_texture->surface();
assert(surface->pixels);
return surface;
}
void OSystem_Android::unlockScreen() {
ENTER();
GLTHREADCHECK;
assert(_game_texture->dirty());
}
void OSystem_Android::setShakePos(int shake_offset) {
ENTER("%d", shake_offset);
if (_shake_offset != shake_offset) {
_shake_offset = shake_offset;
_force_redraw = true;
}
}
void OSystem_Android::fillScreen(uint32 col) {
ENTER("%u", col);
GLTHREADCHECK;
_game_texture->fillBuffer(col);
}
void OSystem_Android::setFocusRectangle(const Common::Rect& rect) {
ENTER("%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();
if (_enable_zoning) {
_focus_rect = Common::Rect();
_force_redraw = true;
}
}
void OSystem_Android::showOverlay() {
ENTER();
_show_overlay = true;
_force_redraw = true;
updateEventScale();
warpMouse(_overlay_texture->width() / 2, _overlay_texture->height() / 2);
GLCALL(glDisable(GL_SCISSOR_TEST));
}
void OSystem_Android::hideOverlay() {
ENTER();
_show_overlay = false;
updateEventScale();
warpMouse(_game_texture->width() / 2, _game_texture->height() / 2);
// double buffered, flip twice
clearScreen(kClearUpdate, 2);
GLCALL(glEnable(GL_SCISSOR_TEST));
}
void OSystem_Android::clearOverlay() {
ENTER();
GLTHREADCHECK;
_overlay_texture->fillBuffer(0);
}
void OSystem_Android::grabOverlay(OverlayColor *buf, int pitch) {
ENTER("%p, %d", buf, pitch);
GLTHREADCHECK;
const Graphics::Surface *surface = _overlay_texture->surface_const();
assert(surface->format.bytesPerPixel == sizeof(buf[0]));
const byte *src = (const byte *)surface->pixels;
uint h = surface->h;
do {
memcpy(buf, src, surface->w * surface->format.bytesPerPixel);
src += surface->pitch;
// This 'pitch' is pixels not bytes
buf += pitch;
} while (--h);
}
void OSystem_Android::copyRectToOverlay(const OverlayColor *buf, int pitch,
int x, int y, int w, int h) {
ENTER("%p, %d, %d, %d, %d, %d", buf, pitch, x, y, w, h);
GLTHREADCHECK;
// 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();
}
Graphics::PixelFormat OSystem_Android::getOverlayFormat() const {
return _overlay_texture->getPixelFormat();
}
bool OSystem_Android::showMouse(bool visible) {
ENTER("%d", visible);
_show_mouse = visible;
return true;
}
void OSystem_Android::setMouseCursor(const byte *buf, uint w, uint h,
int hotspotX, int hotspotY,
uint32 keycolor, int cursorTargetScale,
const Graphics::PixelFormat *format) {
ENTER("%p, %u, %u, %d, %d, %u, %d, %p", buf, w, h, hotspotX, hotspotY,
keycolor, cursorTargetScale, format);
GLTHREADCHECK;
#ifdef USE_RGB_COLOR
if (format && format->bytesPerPixel > 1) {
if (_mouse_texture != _mouse_texture_rgb) {
LOGD("switching to rgb mouse cursor");
assert(!_mouse_texture_rgb);
_mouse_texture_rgb = new GLES5551Texture();
_mouse_texture_rgb->setLinearFilter(_graphicsMode == 1);
}
_mouse_texture = _mouse_texture_rgb;
} else {
if (_mouse_texture != _mouse_texture_palette)
LOGD("switching to paletted mouse cursor");
_mouse_texture = _mouse_texture_palette;
delete _mouse_texture_rgb;
_mouse_texture_rgb = 0;
}
#endif
_mouse_texture->allocBuffer(w, h);
if (_mouse_texture == _mouse_texture_palette) {
assert(keycolor < 256);
byte *p = _mouse_texture_palette->palette() + _mouse_keycolor * 2;
WRITE_UINT16(p, READ_UINT16(p) | 1);
_mouse_keycolor = keycolor;
p = _mouse_texture_palette->palette() + _mouse_keycolor * 2;
WRITE_UINT16(p, READ_UINT16(p) & ~1);
}
if (w == 0 || h == 0)
return;
if (_mouse_texture == _mouse_texture_palette) {
_mouse_texture->updateBuffer(0, 0, w, h, buf, w);
} else {
uint16 pitch = _mouse_texture->pitch();
byte *tmp = new byte[pitch * h];
// meh, a 16bit cursor without alpha bits... this is so silly
if (!crossBlit(tmp, buf, pitch, w * 2, w, h,
_mouse_texture->getPixelFormat(),
*format)) {
LOGE("crossblit failed");
delete[] tmp;
_mouse_texture->allocBuffer(0, 0);
return;
}
uint16 *s = (uint16 *)buf;
uint16 *d = (uint16 *)tmp;
for (uint16 y = 0; y < h; ++y, d += pitch / 2 - w)
for (uint16 x = 0; x < w; ++x, d++)
if (*s++ != (keycolor & 0xffff))
*d |= 1;
_mouse_texture->updateBuffer(0, 0, w, h, tmp, pitch);
delete[] tmp;
}
_mouse_hotspot = Common::Point(hotspotX, hotspotY);
_mouse_targetscale = cursorTargetScale;
}
void OSystem_Android::setCursorPaletteInternal(const byte *colors,
uint start, uint num) {
const Graphics::PixelFormat &pf =
_mouse_texture_palette->getPalettePixelFormat();
byte *p = _mouse_texture_palette->palette() + start * 2;
for (uint i = 0; i < num; ++i, colors += 3, p += 2)
WRITE_UINT16(p, pf.RGBToColor(colors[0], colors[1], colors[2]));
p = _mouse_texture_palette->palette() + _mouse_keycolor * 2;
WRITE_UINT16(p, READ_UINT16(p) & ~1);
}
void OSystem_Android::setCursorPalette(const byte *colors,
uint start, uint num) {
ENTER("%p, %u, %u", colors, start, num);
GLTHREADCHECK;
if (!_mouse_texture->hasPalette()) {
LOGD("switching to paletted mouse cursor");
_mouse_texture = _mouse_texture_palette;
delete _mouse_texture_rgb;
_mouse_texture_rgb = 0;
}
setCursorPaletteInternal(colors, start, num);
_use_mouse_palette = true;
}
void OSystem_Android::disableCursorPalette() {
// when disabling the cursor palette, and we're running a clut8 game,
// it expects the game palette to be used for the cursor
if (_game_texture->hasPalette()) {
const byte *src = _game_texture->palette_const();
byte *dst = _mouse_texture_palette->palette();
const Graphics::PixelFormat &pf_src =
_game_texture->getPalettePixelFormat();
const Graphics::PixelFormat &pf_dst =
_mouse_texture_palette->getPalettePixelFormat();
uint8 r, g, b;
for (uint i = 0; i < 256; ++i, src += 2, dst += 2) {
pf_src.colorToRGB(READ_UINT16(src), r, g, b);
WRITE_UINT16(dst, pf_dst.RGBToColor(r, g, b));
}
byte *p = _mouse_texture_palette->palette() + _mouse_keycolor * 2;
WRITE_UINT16(p, READ_UINT16(p) & ~1);
}
}
#endif

View File

@ -0,0 +1,621 @@
/* 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.
*
*/
#if defined(__ANDROID__)
// Allow use of stuff in <time.h> and abort()
#define FORBIDDEN_SYMBOL_EXCEPTION_time_h
#define FORBIDDEN_SYMBOL_EXCEPTION_abort
// Disable printf override in common/forbidden.h to avoid
// clashes with log.h from the Android SDK.
// That header file uses
// __attribute__ ((format(printf, 3, 4)))
// which gets messed up by our override mechanism; this could
// be avoided by either changing the Android SDK to use the equally
// legal and valid
// __attribute__ ((format(printf, 3, 4)))
// or by refining our printf override to use a varadic macro
// (which then wouldn't be portable, though).
// Anyway, for now we just disable the printf override globally
// for the Android port
#define FORBIDDEN_SYMBOL_EXCEPTION_printf
#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", "(IIIIIII)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/residualvm/residualvm/ResidualVM");
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, int arg6) {
// 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, arg6);
}
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 (value &&
g_engine->hasFeature(Engine::kSupportsSavingDuringRuntime) &&
g_engine->canSaveGameStateCurrently())
g_engine->saveGameState(0, "Android parachute");
}
pause = value;
if (!pause) {
// wake up all threads
for (uint i = 0; i < 3; ++i)
sem_post(&pause_sem);
}
}
#endif

View File

@ -0,0 +1,147 @@
/* 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.
*
*/
#ifndef _ANDROID_JNI_H_
#define _ANDROID_JNI_H_
#if defined(__ANDROID__)
#include <jni.h>
#include <semaphore.h>
#include "common/fs.h"
#include "common/archive.h"
class OSystem_Android;
class JNI {
private:
JNI();
virtual ~JNI();
public:
static bool pause;
static sem_t pause_sem;
static int surface_changeid;
static int egl_surface_width;
static int egl_surface_height;
static jint onLoad(JavaVM *vm);
static JNIEnv *getEnv();
static void attachThread();
static void detachThread();
static void setReadyForEvents(bool ready);
static void getPluginDirectories(Common::FSList &dirs);
static void setWindowCaption(const char *caption);
static void getDPI(float *values);
static void displayMessageOnOSD(const char *msg);
static void showVirtualKeyboard(bool enable);
static void addSysArchivesToSearchSet(Common::SearchSet &s, int priority);
static inline bool haveSurface();
static inline bool swapBuffers();
static bool initSurface();
static void deinitSurface();
static void setAudioPause();
static void setAudioPlay();
static void setAudioStop();
static inline int writeAudio(JNIEnv *env, jbyteArray &data, int offset,
int size);
private:
static JavaVM *_vm;
// back pointer to (java) peer instance
static jobject _jobj;
static jobject _jobj_audio_track;
static jobject _jobj_egl;
static jobject _jobj_egl_display;
static jobject _jobj_egl_surface;
static Common::Archive *_asset_archive;
static OSystem_Android *_system;
static bool _ready_for_events;
static jmethodID _MID_getDPI;
static jmethodID _MID_displayMessageOnOSD;
static jmethodID _MID_setWindowCaption;
static jmethodID _MID_showVirtualKeyboard;
static jmethodID _MID_getSysArchives;
static jmethodID _MID_getPluginDirectories;
static jmethodID _MID_initSurface;
static jmethodID _MID_deinitSurface;
static jmethodID _MID_EGL10_eglSwapBuffers;
static jmethodID _MID_AudioTrack_flush;
static jmethodID _MID_AudioTrack_pause;
static jmethodID _MID_AudioTrack_play;
static jmethodID _MID_AudioTrack_stop;
static jmethodID _MID_AudioTrack_write;
static const JNINativeMethod _natives[];
static void throwByName(JNIEnv *env, const char *name, const char *msg);
static void throwRuntimeException(JNIEnv *env, const char *msg);
// natives for the dark side
static void create(JNIEnv *env, jobject self, jobject asset_manager,
jobject egl, jobject egl_display,
jobject at, jint audio_sample_rate,
jint audio_buffer_size);
static void destroy(JNIEnv *env, jobject self);
static void setSurface(JNIEnv *env, jobject self, jint width, jint height);
static jint main(JNIEnv *env, jobject self, jobjectArray args);
static void pushEvent(JNIEnv *env, jobject self, int type, int arg1,
int arg2, int arg3, int arg4, int arg5, int arg6);
static void enableZoning(JNIEnv *env, jobject self, jboolean enable);
static void setPause(JNIEnv *env, jobject self, jboolean value);
};
inline bool JNI::haveSurface() {
return _jobj_egl_surface != 0;
}
inline bool JNI::swapBuffers() {
JNIEnv *env = JNI::getEnv();
return env->CallBooleanMethod(_jobj_egl, _MID_EGL10_eglSwapBuffers,
_jobj_egl_display, _jobj_egl_surface);
}
inline int JNI::writeAudio(JNIEnv *env, jbyteArray &data, int offset, int size) {
return env->CallIntMethod(_jobj_audio_track, _MID_AudioTrack_write, data,
offset, size);
}
#endif
#endif

View File

@ -0,0 +1,14 @@
MODULE := backends/platform/android
MODULE_OBJS := \
jni.o \
texture.o \
asset-archive.o \
android.o \
gfx.o \
events.o
# We don't use rules.mk but rather manually update OBJS and MODULE_DIRS.
MODULE_OBJS := $(addprefix $(MODULE)/, $(MODULE_OBJS))
OBJS := $(MODULE_OBJS) $(OBJS)
MODULE_DIRS += $(sort $(dir $(MODULE_OBJS)))

View File

@ -0,0 +1,61 @@
package org.residualvm.residualvm;
import android.content.Context;
import android.text.InputType;
import android.util.AttributeSet;
import android.view.SurfaceView;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
public class EditableSurfaceView extends SurfaceView {
public EditableSurfaceView(Context context) {
super(context);
}
public EditableSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public EditableSurfaceView(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
}
@Override
public boolean onCheckIsTextEditor() {
return false;
}
private class MyInputConnection extends BaseInputConnection {
public MyInputConnection() {
super(EditableSurfaceView.this, false);
}
@Override
public boolean performEditorAction(int actionCode) {
if (actionCode == EditorInfo.IME_ACTION_DONE) {
InputMethodManager imm = (InputMethodManager)
getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(getWindowToken(), 0);
}
// Sends enter key
return super.performEditorAction(actionCode);
}
}
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
outAttrs.initialCapsMode = 0;
outAttrs.initialSelEnd = outAttrs.initialSelStart = -1;
outAttrs.inputType = (InputType.TYPE_CLASS_TEXT |
InputType.TYPE_TEXT_VARIATION_NORMAL |
InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE);
outAttrs.imeOptions = (EditorInfo.IME_ACTION_DONE |
EditorInfo.IME_FLAG_NO_EXTRACT_UI);
return new MyInputConnection();
}
}

View File

@ -0,0 +1,63 @@
package org.residualvm.residualvm;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import java.util.ArrayList;
public class PluginProvider extends BroadcastReceiver {
private final static String LOG_TAG = "ResidualVM";
public final static String META_UNPACK_LIB =
"org.residualvm.residualvm.meta.UNPACK_LIB";
public void onReceive(Context context, Intent intent) {
if (!intent.getAction().equals(ResidualVMApplication.ACTION_PLUGIN_QUERY))
return;
Bundle extras = getResultExtras(true);
final ActivityInfo info;
final PackageInfo pinfo;
try {
info = context.getPackageManager()
.getReceiverInfo(new ComponentName(context, this.getClass()),
PackageManager.GET_META_DATA);
pinfo = context.getPackageManager()
.getPackageInfo(context.getPackageName(), 0);
} catch (PackageManager.NameNotFoundException e) {
Log.e(LOG_TAG, "Error finding my own info?", e);
return;
}
String host_version = extras.getString(ResidualVMApplication.EXTRA_VERSION);
if (!pinfo.versionName.equals(host_version)) {
Log.e(LOG_TAG, "Plugin version " + pinfo.versionName + " is not equal to ResidualVM version " + host_version);
return;
}
String mylib = info.metaData.getString(META_UNPACK_LIB);
if (mylib != null) {
ArrayList<String> all_libs =
extras.getStringArrayList(ResidualVMApplication.EXTRA_UNPACK_LIBS);
all_libs.add(new Uri.Builder()
.scheme("plugin")
.authority(context.getPackageName())
.path(mylib)
.toString());
extras.putStringArrayList(ResidualVMApplication.EXTRA_UNPACK_LIBS,
all_libs);
}
setResultExtras(extras);
}
}

View File

@ -0,0 +1,451 @@
package org.residualvm.residualvm;
import android.util.Log;
import android.content.res.AssetManager;
import android.view.SurfaceHolder;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
import javax.microedition.khronos.egl.EGLDisplay;
import javax.microedition.khronos.egl.EGLSurface;
import java.io.File;
import java.util.LinkedHashMap;
public abstract class ResidualVM implements SurfaceHolder.Callback, Runnable {
final protected static String LOG_TAG = "ResidualVM";
final private AssetManager _asset_manager;
final private Object _sem_surface;
private EGL10 _egl;
private EGLDisplay _egl_display = EGL10.EGL_NO_DISPLAY;
private EGLConfig _egl_config;
private EGLContext _egl_context = EGL10.EGL_NO_CONTEXT;
private EGLSurface _egl_surface = EGL10.EGL_NO_SURFACE;
private SurfaceHolder _surface_holder;
private AudioTrack _audio_track;
private int _sample_rate = 0;
private int _buffer_size = 0;
private String[] _args;
final private native void create(AssetManager asset_manager,
EGL10 egl, EGLDisplay egl_display,
AudioTrack audio_track,
int sample_rate, int buffer_size);
final private native void destroy();
final private native void setSurface(int width, int height);
final private native int main(String[] args);
// pause the engine and all native threads
final public native void setPause(boolean pause);
final public native void enableZoning(boolean enable);
// Feed an event to ResidualVM. Safe to call from other threads.
final public native void pushEvent(int type, int arg1, int arg2, int arg3,
int arg4, int arg5, int arg6);
// Callbacks from C++ peer instance
abstract protected void getDPI(float[] values);
abstract protected void displayMessageOnOSD(String msg);
abstract protected void setWindowCaption(String caption);
abstract protected String[] getPluginDirectories();
abstract protected void showVirtualKeyboard(boolean enable);
abstract protected String[] getSysArchives();
public ResidualVM(AssetManager asset_manager, SurfaceHolder holder) {
_asset_manager = asset_manager;
_sem_surface = new Object();
holder.addCallback(this);
}
// SurfaceHolder callback
final public void surfaceCreated(SurfaceHolder holder) {
Log.d(LOG_TAG, "surfaceCreated");
// no need to do anything, surfaceChanged() will be called in any case
}
// SurfaceHolder callback
final public void surfaceChanged(SurfaceHolder holder, int format,
int width, int height) {
// the orientation may reset on standby mode and the theme manager
// could assert when using a portrait resolution. so lets not do that.
if (height > width) {
Log.d(LOG_TAG, String.format("Ignoring surfaceChanged: %dx%d (%d)",
width, height, format));
return;
}
Log.d(LOG_TAG, String.format("surfaceChanged: %dx%d (%d)",
width, height, format));
synchronized(_sem_surface) {
_surface_holder = holder;
_sem_surface.notifyAll();
}
// store values for the native code
setSurface(width, height);
}
// SurfaceHolder callback
final public void surfaceDestroyed(SurfaceHolder holder) {
Log.d(LOG_TAG, "surfaceDestroyed");
synchronized(_sem_surface) {
_surface_holder = null;
_sem_surface.notifyAll();
}
// clear values for the native code
setSurface(0, 0);
}
final public void setArgs(String[] args) {
_args = args;
}
final public void run() {
try {
initAudio();
initEGL();
// wait for the surfaceChanged callback
synchronized(_sem_surface) {
while (_surface_holder == null)
_sem_surface.wait();
}
} catch (Exception e) {
deinitEGL();
deinitAudio();
throw new RuntimeException("Error preparing the ResidualVM thread", e);
}
create(_asset_manager, _egl, _egl_display,
_audio_track, _sample_rate, _buffer_size);
int res = main(_args);
destroy();
deinitEGL();
deinitAudio();
// On exit, tear everything down for a fresh restart next time.
System.exit(res);
}
final private void initEGL() throws Exception {
_egl = (EGL10)EGLContext.getEGL();
_egl_display = _egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
int[] version = new int[2];
_egl.eglInitialize(_egl_display, version);
int[] num_config = new int[1];
_egl.eglGetConfigs(_egl_display, null, 0, num_config);
final int numConfigs = num_config[0];
if (numConfigs <= 0)
throw new IllegalArgumentException("No EGL configs");
EGLConfig[] configs = new EGLConfig[numConfigs];
_egl.eglGetConfigs(_egl_display, configs, numConfigs, num_config);
// Android's eglChooseConfig is busted in several versions and
// devices so we have to filter/rank the configs ourselves.
_egl_config = chooseEglConfig(configs);
_egl_context = _egl.eglCreateContext(_egl_display, _egl_config,
EGL10.EGL_NO_CONTEXT, null);
if (_egl_context == EGL10.EGL_NO_CONTEXT)
throw new Exception(String.format("Failed to create context: 0x%x",
_egl.eglGetError()));
}
// Callback from C++ peer instance
final protected EGLSurface initSurface() throws Exception {
_egl_surface = _egl.eglCreateWindowSurface(_egl_display, _egl_config,
_surface_holder, null);
if (_egl_surface == EGL10.EGL_NO_SURFACE)
throw new Exception(String.format(
"eglCreateWindowSurface failed: 0x%x", _egl.eglGetError()));
_egl.eglMakeCurrent(_egl_display, _egl_surface, _egl_surface,
_egl_context);
GL10 gl = (GL10)_egl_context.getGL();
Log.i(LOG_TAG, String.format("Using EGL %s (%s); GL %s/%s (%s)",
_egl.eglQueryString(_egl_display, EGL10.EGL_VERSION),
_egl.eglQueryString(_egl_display, EGL10.EGL_VENDOR),
gl.glGetString(GL10.GL_VERSION),
gl.glGetString(GL10.GL_RENDERER),
gl.glGetString(GL10.GL_VENDOR)));
return _egl_surface;
}
// Callback from C++ peer instance
final protected void deinitSurface() {
if (_egl_display != EGL10.EGL_NO_DISPLAY) {
_egl.eglMakeCurrent(_egl_display, EGL10.EGL_NO_SURFACE,
EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
if (_egl_surface != EGL10.EGL_NO_SURFACE)
_egl.eglDestroySurface(_egl_display, _egl_surface);
}
_egl_surface = EGL10.EGL_NO_SURFACE;
}
final private void deinitEGL() {
if (_egl_display != EGL10.EGL_NO_DISPLAY) {
_egl.eglMakeCurrent(_egl_display, EGL10.EGL_NO_SURFACE,
EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
if (_egl_surface != EGL10.EGL_NO_SURFACE)
_egl.eglDestroySurface(_egl_display, _egl_surface);
if (_egl_context != EGL10.EGL_NO_CONTEXT)
_egl.eglDestroyContext(_egl_display, _egl_context);
_egl.eglTerminate(_egl_display);
}
_egl_surface = EGL10.EGL_NO_SURFACE;
_egl_context = EGL10.EGL_NO_CONTEXT;
_egl_config = null;
_egl_display = EGL10.EGL_NO_DISPLAY;
_egl = null;
}
final private void initAudio() throws Exception {
_sample_rate = AudioTrack.getNativeOutputSampleRate(
AudioManager.STREAM_MUSIC);
_buffer_size = AudioTrack.getMinBufferSize(_sample_rate,
AudioFormat.CHANNEL_CONFIGURATION_STEREO,
AudioFormat.ENCODING_PCM_16BIT);
// ~50ms
int buffer_size_want = (_sample_rate * 2 * 2 / 20) & ~1023;
if (_buffer_size < buffer_size_want) {
Log.w(LOG_TAG, String.format(
"adjusting audio buffer size (was: %d)", _buffer_size));
_buffer_size = buffer_size_want;
}
Log.i(LOG_TAG, String.format("Using %d bytes buffer for %dHz audio",
_buffer_size, _sample_rate));
_audio_track = new AudioTrack(AudioManager.STREAM_MUSIC,
_sample_rate,
AudioFormat.CHANNEL_CONFIGURATION_STEREO,
AudioFormat.ENCODING_PCM_16BIT,
_buffer_size,
AudioTrack.MODE_STREAM);
if (_audio_track.getState() != AudioTrack.STATE_INITIALIZED)
throw new Exception(
String.format("Error initializing AudioTrack: %d",
_audio_track.getState()));
}
final private void deinitAudio() {
if (_audio_track != null)
_audio_track.stop();
_audio_track = null;
_buffer_size = 0;
_sample_rate = 0;
}
private static final int[] s_eglAttribs = {
EGL10.EGL_CONFIG_ID,
EGL10.EGL_BUFFER_SIZE,
EGL10.EGL_RED_SIZE,
EGL10.EGL_GREEN_SIZE,
EGL10.EGL_BLUE_SIZE,
EGL10.EGL_ALPHA_SIZE,
EGL10.EGL_CONFIG_CAVEAT,
EGL10.EGL_DEPTH_SIZE,
EGL10.EGL_LEVEL,
EGL10.EGL_MAX_PBUFFER_WIDTH,
EGL10.EGL_MAX_PBUFFER_HEIGHT,
EGL10.EGL_MAX_PBUFFER_PIXELS,
EGL10.EGL_NATIVE_RENDERABLE,
EGL10.EGL_NATIVE_VISUAL_ID,
EGL10.EGL_NATIVE_VISUAL_TYPE,
EGL10.EGL_SAMPLE_BUFFERS,
EGL10.EGL_SAMPLES,
EGL10.EGL_STENCIL_SIZE,
EGL10.EGL_SURFACE_TYPE,
EGL10.EGL_TRANSPARENT_TYPE,
EGL10.EGL_TRANSPARENT_RED_VALUE,
EGL10.EGL_TRANSPARENT_GREEN_VALUE,
EGL10.EGL_TRANSPARENT_BLUE_VALUE
};
final private class EglAttribs extends LinkedHashMap<Integer, Integer> {
public EglAttribs(EGLConfig config) {
super(s_eglAttribs.length);
int[] value = new int[1];
for (int i : s_eglAttribs) {
_egl.eglGetConfigAttrib(_egl_display, config, i, value);
put(i, value[0]);
}
}
private int weightBits(int attr, int size) {
final int value = get(attr);
int score = 0;
if (value == size || (size > 0 && value > size))
score += 10;
// penalize for wasted bits
score -= value - size;
return score;
}
public int weight() {
int score = 10000;
if (get(EGL10.EGL_CONFIG_CAVEAT) != EGL10.EGL_NONE)
score -= 1000;
// less MSAA is better
score -= get(EGL10.EGL_SAMPLES) * 100;
// Must be at least 565, but then smaller is better
score += weightBits(EGL10.EGL_RED_SIZE, 5);
score += weightBits(EGL10.EGL_GREEN_SIZE, 6);
score += weightBits(EGL10.EGL_BLUE_SIZE, 5);
score += weightBits(EGL10.EGL_ALPHA_SIZE, 0);
score += weightBits(EGL10.EGL_DEPTH_SIZE, 0);
score += weightBits(EGL10.EGL_STENCIL_SIZE, 0);
return score;
}
public String toString() {
String s;
if (get(EGL10.EGL_ALPHA_SIZE) > 0)
s = String.format("[%d] RGBA%d%d%d%d",
get(EGL10.EGL_CONFIG_ID),
get(EGL10.EGL_RED_SIZE),
get(EGL10.EGL_GREEN_SIZE),
get(EGL10.EGL_BLUE_SIZE),
get(EGL10.EGL_ALPHA_SIZE));
else
s = String.format("[%d] RGB%d%d%d",
get(EGL10.EGL_CONFIG_ID),
get(EGL10.EGL_RED_SIZE),
get(EGL10.EGL_GREEN_SIZE),
get(EGL10.EGL_BLUE_SIZE));
if (get(EGL10.EGL_DEPTH_SIZE) > 0)
s += String.format(" D%d", get(EGL10.EGL_DEPTH_SIZE));
if (get(EGL10.EGL_STENCIL_SIZE) > 0)
s += String.format(" S%d", get(EGL10.EGL_STENCIL_SIZE));
if (get(EGL10.EGL_SAMPLES) > 0)
s += String.format(" MSAAx%d", get(EGL10.EGL_SAMPLES));
if ((get(EGL10.EGL_SURFACE_TYPE) & EGL10.EGL_WINDOW_BIT) > 0)
s += " W";
if ((get(EGL10.EGL_SURFACE_TYPE) & EGL10.EGL_PBUFFER_BIT) > 0)
s += " P";
if ((get(EGL10.EGL_SURFACE_TYPE) & EGL10.EGL_PIXMAP_BIT) > 0)
s += " X";
switch (get(EGL10.EGL_CONFIG_CAVEAT)) {
case EGL10.EGL_NONE:
break;
case EGL10.EGL_SLOW_CONFIG:
s += " SLOW";
break;
case EGL10.EGL_NON_CONFORMANT_CONFIG:
s += " NON_CONFORMANT";
default:
s += String.format(" unknown CAVEAT 0x%x",
get(EGL10.EGL_CONFIG_CAVEAT));
}
return s;
}
};
final private EGLConfig chooseEglConfig(EGLConfig[] configs) {
EGLConfig res = configs[0];
int bestScore = -1;
Log.d(LOG_TAG, "EGL configs:");
for (EGLConfig config : configs) {
EglAttribs attr = new EglAttribs(config);
// must have
if ((attr.get(EGL10.EGL_SURFACE_TYPE) & EGL10.EGL_WINDOW_BIT) == 0)
continue;
int score = attr.weight();
Log.d(LOG_TAG, String.format("%s (%d)", attr.toString(), score));
if (score > bestScore) {
res = config;
bestScore = score;
}
}
if (bestScore < 0)
Log.e(LOG_TAG,
"Unable to find an acceptable EGL config, expect badness.");
Log.d(LOG_TAG, String.format("Chosen EGL config: %s",
new EglAttribs(res).toString()));
return res;
}
static {
// For grabbing with gdb...
final boolean sleep_for_debugger = false;
if (sleep_for_debugger) {
try {
Thread.sleep(20 * 1000);
} catch (InterruptedException e) {
}
}
File cache_dir = ResidualVMApplication.getLastCacheDir();
String libname = System.mapLibraryName("residualvm");
File libpath = new File(cache_dir, libname);
System.load(libpath.getPath());
}
}

View File

@ -0,0 +1,243 @@
package org.residualvm.residualvm;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.media.AudioManager;
import android.os.Bundle;
import android.os.Environment;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.SurfaceView;
import android.view.SurfaceHolder;
import android.view.MotionEvent;
import android.view.inputmethod.InputMethodManager;
import android.widget.Toast;
import java.io.File;
public class ResidualVMActivity extends Activity {
private class MyResidualVM extends ResidualVM {
private boolean usingSmallScreen() {
// Multiple screen sizes came in with Android 1.6. Have
// to use reflection in order to continue supporting 1.5
// devices :(
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
try {
// This 'density' term is very confusing.
int DENSITY_LOW = metrics.getClass().getField("DENSITY_LOW").getInt(null);
int densityDpi = metrics.getClass().getField("densityDpi").getInt(metrics);
return densityDpi <= DENSITY_LOW;
} catch (Exception e) {
return false;
}
}
public MyResidualVM(SurfaceHolder holder) {
super(ResidualVMActivity.this.getAssets(), holder);
// Enable ResidualVM zoning on 'small' screens.
// FIXME make this optional for the user
// disabled for now since it crops too much
//enableZoning(usingSmallScreen());
}
@Override
protected void getDPI(float[] values) {
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
values[0] = metrics.xdpi;
values[1] = metrics.ydpi;
}
@Override
protected void displayMessageOnOSD(String msg) {
Log.i(LOG_TAG, "OSD: " + msg);
Toast.makeText(ResidualVMActivity.this, msg, Toast.LENGTH_LONG).show();
}
@Override
protected void setWindowCaption(final String caption) {
runOnUiThread(new Runnable() {
public void run() {
setTitle(caption);
}
});
}
@Override
protected String[] getPluginDirectories() {
String[] dirs = new String[1];
dirs[0] = ResidualVMApplication.getLastCacheDir().getPath();
return dirs;
}
@Override
protected void showVirtualKeyboard(final boolean enable) {
runOnUiThread(new Runnable() {
public void run() {
showKeyboard(enable);
}
});
}
@Override
protected String[] getSysArchives() {
return new String[0];
}
}
private MyResidualVM _residualvm;
private ResidualVMEvents _events;
private Thread _residualvm_thread;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
setContentView(R.layout.main);
takeKeyEvents(true);
// This is a common enough error that we should warn about it
// explicitly.
if (!Environment.getExternalStorageDirectory().canRead()) {
new AlertDialog.Builder(this)
.setTitle(R.string.no_sdcard_title)
.setIcon(android.R.drawable.ic_dialog_alert)
.setMessage(R.string.no_sdcard)
.setNegativeButton(R.string.quit,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int which) {
finish();
}
})
.show();
return;
}
SurfaceView main_surface = (SurfaceView)findViewById(R.id.main_surface);
main_surface.requestFocus();
getFilesDir().mkdirs();
// Store savegames on external storage if we can, which means they're
// world-readable and don't get deleted on uninstall.
String savePath = Environment.getExternalStorageDirectory() + "/ResidualVM/Saves/";
File saveDir = new File(savePath);
saveDir.mkdirs();
if (!saveDir.isDirectory()) {
// If it doesn't work, resort to the internal app path.
savePath = getDir("saves", MODE_WORLD_READABLE).getPath();
}
// Start ResidualVM
_residualvm = new MyResidualVM(main_surface.getHolder());
_residualvm.setArgs(new String[] {
"ResidualVM",
"--config=" + getFileStreamPath("residualvmrc").getPath(),
"--path=" + Environment.getExternalStorageDirectory().getPath(),
"--gui-theme=modern",
"--savepath=" + savePath
});
_events = new ResidualVMEvents(this, _residualvm);
main_surface.setOnKeyListener(_events);
_residualvm_thread = new Thread(_residualvm, "ResidualVM");
_residualvm_thread.start();
}
@Override
public void onStart() {
Log.d(ResidualVM.LOG_TAG, "onStart");
super.onStart();
}
@Override
public void onResume() {
Log.d(ResidualVM.LOG_TAG, "onResume");
super.onResume();
if (_residualvm != null)
_residualvm.setPause(false);
}
@Override
public void onPause() {
Log.d(ResidualVM.LOG_TAG, "onPause");
super.onPause();
if (_residualvm != null)
_residualvm.setPause(true);
}
@Override
public void onStop() {
Log.d(ResidualVM.LOG_TAG, "onStop");
super.onStop();
}
@Override
public void onDestroy() {
Log.d(ResidualVM.LOG_TAG, "onDestroy");
super.onDestroy();
if (_events != null) {
_events.sendQuitEvent();
try {
// 1s timeout
_residualvm_thread.join(1000);
} catch (InterruptedException e) {
Log.i(ResidualVM.LOG_TAG, "Error while joining ResidualVM thread", e);
}
_residualvm = null;
}
}
@Override
public boolean onTrackballEvent(MotionEvent e) {
if (_events != null)
return _events.onTrackballEvent(e);
return false;
}
@Override
public boolean onTouchEvent(MotionEvent e) {
if (_events != null)
return _events.onTouchEvent(e);
return false;
}
private void showKeyboard(boolean show) {
SurfaceView main_surface = (SurfaceView)findViewById(R.id.main_surface);
InputMethodManager imm = (InputMethodManager)
getSystemService(INPUT_METHOD_SERVICE);
if (show)
imm.showSoftInput(main_surface, InputMethodManager.SHOW_IMPLICIT);
else
imm.hideSoftInputFromWindow(main_surface.getWindowToken(),
InputMethodManager.HIDE_IMPLICIT_ONLY);
}
}

View File

@ -0,0 +1,31 @@
package org.residualvm.residualvm;
import android.app.Application;
import java.io.File;
public class ResidualVMApplication extends Application {
public final static String ACTION_PLUGIN_QUERY = "org.residualvm.residualvm.action.PLUGIN_QUERY";
public final static String EXTRA_UNPACK_LIBS = "org.residualvm.residualvm.extra.UNPACK_LIBS";
public final static String EXTRA_VERSION = "org.residualvm.residualvm.extra.VERSION";
private static File _cache_dir;
@Override
public void onCreate() {
super.onCreate();
// This is still on /data :(
_cache_dir = getCacheDir();
// This is mounted noexec :(
//cache_dir = new File(Environment.getExternalStorageDirectory(),
// "/.ResidualVM.tmp");
// This is owned by download manager and requires special
// permissions to access :(
//cache_dir = Environment.getDownloadCacheDirectory();
}
public static File getLastCacheDir() {
return _cache_dir;
}
}

View File

@ -0,0 +1,236 @@
package org.residualvm.residualvm;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.content.Context;
import android.view.KeyEvent;
import android.view.KeyCharacterMap;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.GestureDetector;
import android.view.inputmethod.InputMethodManager;
public class ResidualVMEvents implements
android.view.View.OnKeyListener,
android.view.GestureDetector.OnGestureListener,
android.view.GestureDetector.OnDoubleTapListener {
public static final int JE_SYS_KEY = 0;
public static final int JE_KEY = 1;
public static final int JE_DPAD = 2;
public static final int JE_DOWN = 3;
public static final int JE_SCROLL = 4;
public static final int JE_TAP = 5;
public static final int JE_DOUBLE_TAP = 6;
public static final int JE_MULTI = 7;
public static final int JE_BALL = 8;
public static final int JE_TOUCH = 9;
public static final int JE_LONG = 10;
public static final int JE_FLING = 11;
public static final int JE_QUIT = 0x1000;
final protected Context _context;
final protected ResidualVM _residualvm;
final protected GestureDetector _gd;
final protected int _longPress;
public ResidualVMEvents(Context context, ResidualVM residualvm) {
_context = context;
_residualvm = residualvm;
_gd = new GestureDetector(context, this);
_gd.setOnDoubleTapListener(this);
//_gd.setIsLongpressEnabled(false);
_longPress = ViewConfiguration.getLongPressTimeout();
}
final public void sendQuitEvent() {
_residualvm.pushEvent(JE_QUIT, 0, 0, 0, 0, 0, 0);
}
public boolean onTrackballEvent(MotionEvent e) {
_residualvm.pushEvent(JE_BALL, e.getAction(),
(int)(e.getX() * e.getXPrecision() * 100),
(int)(e.getY() * e.getYPrecision() * 100),
0, 0, 0);
return true;
}
final static int MSG_MENU_LONG_PRESS = 1;
final private Handler keyHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == MSG_MENU_LONG_PRESS) {
InputMethodManager imm = (InputMethodManager)
_context.getSystemService(_context.INPUT_METHOD_SERVICE);
if (imm != null)
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
}
}
};
// OnKeyListener
final public boolean onKey(View v, int keyCode, KeyEvent e) {
final int action = e.getAction();
if (e.isSystem()) {
// filter what we handle
switch (keyCode) {
case KeyEvent.KEYCODE_BACK:
case KeyEvent.KEYCODE_MENU:
case KeyEvent.KEYCODE_CAMERA:
case KeyEvent.KEYCODE_SEARCH:
break;
default:
return false;
}
// no repeats for system keys
if (e.getRepeatCount() > 0)
return false;
// Have to reimplement hold-down-menu-brings-up-softkeybd
// ourselves, since we are otherwise hijacking the menu key :(
// See com.android.internal.policy.impl.PhoneWindow.onKeyDownPanel()
// for the usual Android implementation of this feature.
if (keyCode == KeyEvent.KEYCODE_MENU) {
final boolean fired =
!keyHandler.hasMessages(MSG_MENU_LONG_PRESS);
keyHandler.removeMessages(MSG_MENU_LONG_PRESS);
if (action == KeyEvent.ACTION_DOWN) {
keyHandler.sendMessageDelayed(keyHandler.obtainMessage(
MSG_MENU_LONG_PRESS), _longPress);
return true;
}
if (fired)
return true;
// only send up events of the menu button to the native side
if (action != KeyEvent.ACTION_UP)
return true;
}
_residualvm.pushEvent(JE_SYS_KEY, action, keyCode, 0, 0, 0, 0);
return true;
}
// sequence of characters
if (action == KeyEvent.ACTION_MULTIPLE &&
keyCode == KeyEvent.KEYCODE_UNKNOWN) {
final KeyCharacterMap m = KeyCharacterMap.load(e.getDeviceId());
final KeyEvent[] es = m.getEvents(e.getCharacters().toCharArray());
if (es == null)
return true;
for (KeyEvent s : es) {
_residualvm.pushEvent(JE_KEY, s.getAction(), s.getKeyCode(),
s.getUnicodeChar() & KeyCharacterMap.COMBINING_ACCENT_MASK,
s.getMetaState(), s.getRepeatCount(), 0);
}
return true;
}
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_UP:
case KeyEvent.KEYCODE_DPAD_DOWN:
case KeyEvent.KEYCODE_DPAD_LEFT:
case KeyEvent.KEYCODE_DPAD_RIGHT:
case KeyEvent.KEYCODE_DPAD_CENTER:
_residualvm.pushEvent(JE_DPAD, action, keyCode,
(int)(e.getEventTime() - e.getDownTime()),
e.getRepeatCount(), 0, 0);
return true;
}
_residualvm.pushEvent(JE_KEY, action, keyCode,
e.getUnicodeChar() & KeyCharacterMap.COMBINING_ACCENT_MASK,
e.getMetaState(), e.getRepeatCount(), 0);
return true;
}
final public boolean onTouchEvent(MotionEvent e) {
final int action = e.getAction();
// constants from APIv5:
// (action & ACTION_POINTER_INDEX_MASK) >> ACTION_POINTER_INDEX_SHIFT
final int pointer = (action & 0xff00) >> 8;
final int x0 = (int)e.getX();
final int y0 = (int)e.getY();
final int x1 = (e.getPointerCount() > 1 ? (int)e.getX(1) : -1);
final int y1 = (e.getPointerCount() > 1 ? (int)e.getY(1) : -1);
if (pointer > 0) {
_residualvm.pushEvent(JE_MULTI, pointer, action & 0xff, // ACTION_MASK
x0, y0, x1, y1);
} else {
_residualvm.pushEvent(JE_TOUCH, pointer, action & 0xff, // ACTION_MASK
x0, y0, x1, y1);
}
return _gd.onTouchEvent(e);
}
// OnGestureListener
final public boolean onDown(MotionEvent e) {
_residualvm.pushEvent(JE_DOWN, (int)e.getX(), (int)e.getY(), 0, 0, 0, 0);
return true;
}
final public boolean onFling(MotionEvent e1, MotionEvent e2,
float velocityX, float velocityY) {
_residualvm.pushEvent(JE_FLING, (int)e1.getX(), (int)e1.getY(),
(int)e2.getX(), (int)e2.getY(), 0, 0);
return true;
}
final public void onLongPress(MotionEvent e) {
_residualvm.pushEvent(JE_LONG, (int)e.getX(), (int)e.getY(),
0, 0, 0, 0);
}
final public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
_residualvm.pushEvent(JE_SCROLL, (int)e1.getX(), (int)e1.getY(),
(int)e2.getX(), (int)e2.getY(), 0, 0);
return true;
}
final public void onShowPress(MotionEvent e) {
}
final public boolean onSingleTapUp(MotionEvent e) {
return false;
}
// OnDoubleTapListener
final public boolean onDoubleTap(MotionEvent e) {
return false;
}
final public boolean onDoubleTapEvent(MotionEvent e) {
_residualvm.pushEvent(JE_DOUBLE_TAP, (int)e.getX(), (int)e.getY(),
e.getAction(), 0, 0, 0);
return true;
}
final public boolean onSingleTapConfirmed(MotionEvent e) {
_residualvm.pushEvent(JE_TAP, (int)e.getX(), (int)e.getY(),
(int)(e.getEventTime() - e.getDownTime()), 0, 0, 0);
return true;
}
}

View File

@ -0,0 +1,388 @@
package org.residualvm.residualvm;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.widget.ProgressBar;
import java.io.IOException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipFile;
import java.util.zip.ZipEntry;
public class Unpacker extends Activity {
protected final static String LOG_TAG = "ResidualVM";
// TODO don't hardcode this
private final static boolean PLUGINS_ENABLED = false;
private final static String META_NEXT_ACTIVITY =
"org.residualvm.unpacker.nextActivity";
private ProgressBar mProgress;
private File mUnpackDest; // location to unpack into
private AsyncTask<String, Integer, Void> mUnpacker;
private final static int REQUEST_MARKET = 1;
// Android 3.1+ only
public static final int FLAG_INCLUDE_STOPPED_PACKAGES = 32;
private static class UnpackJob {
public ZipFile zipfile;
public Set<String> paths;
public UnpackJob(ZipFile zipfile, Set<String> paths) {
this.zipfile = zipfile;
this.paths = paths;
}
public long UnpackSize() {
long size = 0;
for (String path: paths) {
ZipEntry entry = zipfile.getEntry(path);
if (entry != null) size += entry.getSize();
}
return size;
}
}
private class UnpackTask extends AsyncTask<String, Integer, Void> {
@Override
protected void onProgressUpdate(Integer... progress) {
mProgress.setIndeterminate(false);
mProgress.setMax(progress[1]);
mProgress.setProgress(progress[0]);
mProgress.postInvalidate();
}
@Override
protected void onPostExecute(Void result) {
Bundle md = getMetaData();
String nextActivity = md.getString(META_NEXT_ACTIVITY);
if (nextActivity != null) {
final ComponentName cn =
ComponentName.unflattenFromString(nextActivity);
if (cn != null) {
final Intent origIntent = getIntent();
Intent intent = new Intent();
intent.setComponent(cn);
if (origIntent.getExtras() != null)
intent.putExtras(origIntent.getExtras());
intent.putExtra(Intent.EXTRA_INTENT, origIntent);
intent.setDataAndType(origIntent.getData(),
origIntent.getType());
//intent.fillIn(getIntent(), 0);
intent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
Log.i(LOG_TAG,
"Starting next activity with intent " + intent);
startActivity(intent);
} else {
Log.w(LOG_TAG,
"Unable to extract a component name from " + nextActivity);
}
}
finish();
}
@Override
protected Void doInBackground(String... all_libs) {
// This will contain all unpack jobs
Map<String, UnpackJob> unpack_jobs =
new HashMap<String, UnpackJob>(all_libs.length);
// This will contain all unpack filenames (so we can
// detect stale files in the unpack directory)
Set<String> all_files = new HashSet<String>(all_libs.length);
for (String lib: all_libs) {
final Uri uri = Uri.parse(lib);
final String pkg = uri.getAuthority();
final String path = uri.getPath().substring(1); // skip first /
all_files.add(new File(path).getName());
UnpackJob job = unpack_jobs.get(pkg);
if (job == null) {
try {
// getPackageResourcePath is hidden in Context,
// but exposed in ContextWrapper...
ContextWrapper context =
new ContextWrapper(createPackageContext(pkg, 0));
ZipFile zipfile =
new ZipFile(context.getPackageResourcePath());
job = new UnpackJob(zipfile, new HashSet<String>(1));
} catch (PackageManager.NameNotFoundException e) {
Log.e(LOG_TAG, "Package " + pkg +
" not found", e);
continue;
} catch (IOException e) {
// FIXME: show some sort of GUI error dialog
Log.e(LOG_TAG,
"Error opening ZIP for package " + pkg, e);
continue;
}
unpack_jobs.put(pkg, job);
}
job.paths.add(path);
}
// Delete stale filenames from mUnpackDest
for (File file: mUnpackDest.listFiles()) {
if (!all_files.contains(file.getName())) {
Log.i(LOG_TAG,
"Deleting stale cached file " + file);
file.delete();
}
}
int total_size = 0;
for (UnpackJob job: unpack_jobs.values())
total_size += job.UnpackSize();
publishProgress(0, total_size);
mUnpackDest.mkdirs();
int progress = 0;
for (UnpackJob job: unpack_jobs.values()) {
try {
ZipFile zipfile = job.zipfile;
for (String path: job.paths) {
ZipEntry zipentry = zipfile.getEntry(path);
if (zipentry == null)
throw new FileNotFoundException(
"Couldn't find " + path + " in zip");
File dest = new File(mUnpackDest, new File(path).getName());
if (dest.exists() &&
dest.lastModified() == zipentry.getTime() &&
dest.length() == zipentry.getSize()) {
// Already unpacked
progress += zipentry.getSize();
} else {
if (dest.exists())
Log.d(LOG_TAG,
"Replacing " + dest.getPath() +
" old.mtime=" + dest.lastModified() +
" new.mtime=" + zipentry.getTime() +
" old.size=" + dest.length() +
" new.size=" + zipentry.getSize());
else
Log.i(LOG_TAG,
"Extracting " + zipentry.getName() +
" from " + zipfile.getName() +
" to " + dest.getPath());
long next_update = progress;
InputStream in = zipfile.getInputStream(zipentry);
OutputStream out = new FileOutputStream(dest);
int len;
byte[] buffer = new byte[4096];
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
progress += len;
if (progress >= next_update) {
publishProgress(progress, total_size);
// Arbitrary limit of 2% update steps
next_update += total_size / 50;
}
}
in.close();
out.close();
dest.setLastModified(zipentry.getTime());
}
publishProgress(progress, total_size);
}
zipfile.close();
} catch (IOException e) {
// FIXME: show some sort of GUI error dialog
Log.e(LOG_TAG, "Error unpacking plugin", e);
}
}
if (progress != total_size)
Log.d(LOG_TAG, "Ended with progress " + progress +
" != total size " + total_size);
setResult(RESULT_OK);
return null;
}
}
private class PluginBroadcastReciever extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (!intent.getAction()
.equals(ResidualVMApplication.ACTION_PLUGIN_QUERY)) {
Log.e(LOG_TAG,
"Received unexpected action " + intent.getAction());
return;
}
Bundle extras = getResultExtras(false);
if (extras == null) {
// Nothing for us to do.
Unpacker.this.setResult(RESULT_OK);
finish();
}
ArrayList<String> unpack_libs =
extras.getStringArrayList(ResidualVMApplication.EXTRA_UNPACK_LIBS);
if (unpack_libs != null && !unpack_libs.isEmpty()) {
final String[] libs =
unpack_libs.toArray(new String[unpack_libs.size()]);
mUnpacker = new UnpackTask().execute(libs);
}
}
}
private void initPlugins() {
Bundle extras = new Bundle(1);
ArrayList<String> unpack_libs = new ArrayList<String>(1);
// This is the common ResidualVM code (not really a "plugin" as such)
unpack_libs.add(new Uri.Builder()
.scheme("plugin")
.authority(getPackageName())
.path("mylib/armeabi/libresidualvm.so")
.toString());
extras.putStringArrayList(ResidualVMApplication.EXTRA_UNPACK_LIBS,
unpack_libs);
final PackageInfo info;
try {
info = getPackageManager().getPackageInfo(getPackageName(), 0);
} catch (PackageManager.NameNotFoundException e) {
Log.e(LOG_TAG, "Error finding my own info?", e);
return;
}
extras.putString(ResidualVMApplication.EXTRA_VERSION, info.versionName);
Intent intent = new Intent(ResidualVMApplication.ACTION_PLUGIN_QUERY);
// Android 3.1 defaults to FLAG_EXCLUDE_STOPPED_PACKAGES, and since
// none of our plugins will ever be running, that is not helpful
intent.setFlags(FLAG_INCLUDE_STOPPED_PACKAGES);
sendOrderedBroadcast(intent, Manifest.permission.RESIDUALVM_PLUGIN,
new PluginBroadcastReciever(),
null, RESULT_OK, null, extras);
}
@Override
public void onCreate(Bundle b) {
super.onCreate(b);
mUnpackDest = ResidualVMApplication.getLastCacheDir();
setContentView(R.layout.splash);
mProgress = (ProgressBar)findViewById(R.id.progress);
setResult(RESULT_CANCELED);
tryUnpack();
}
private void tryUnpack() {
Intent intent = new Intent(ResidualVMApplication.ACTION_PLUGIN_QUERY);
List<ResolveInfo> plugins = getPackageManager()
.queryBroadcastReceivers(intent, 0);
if (PLUGINS_ENABLED && plugins.isEmpty()) {
// No plugins installed
AlertDialog.Builder alert = new AlertDialog.Builder(this)
.setTitle(R.string.no_plugins_title)
.setMessage(R.string.no_plugins_found)
.setIcon(android.R.drawable.ic_dialog_alert)
.setOnCancelListener(new DialogInterface.OnCancelListener() {
public void onCancel(DialogInterface dialog) {
finish();
}
})
.setNegativeButton(R.string.quit,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
finish();
}
});
final Uri uri = Uri.parse("market://search?q=ResidualVM plugin");
final Intent market_intent = new Intent(Intent.ACTION_VIEW, uri);
if (getPackageManager().resolveActivity(market_intent, 0) != null) {
alert.setPositiveButton(R.string.to_market,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
try {
startActivityForResult(market_intent,
REQUEST_MARKET);
} catch (ActivityNotFoundException e) {
Log.e(LOG_TAG,
"Error starting market", e);
}
}
});
}
alert.show();
} else {
// Already have at least one plugin installed
initPlugins();
}
}
@Override
public void onStop() {
if (mUnpacker != null)
mUnpacker.cancel(true);
super.onStop();
}
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
switch (requestCode) {
case REQUEST_MARKET:
if (resultCode != RESULT_OK)
Log.w(LOG_TAG, "Market returned " + resultCode);
tryUnpack();
break;
}
}
private Bundle getMetaData() {
try {
ActivityInfo ai = getPackageManager()
.getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
return ai.metaData;
} catch (PackageManager.NameNotFoundException e) {
Log.w(LOG_TAG, "Unable to find my own meta-data", e);
return new Bundle();
}
}
}

View File

@ -0,0 +1,490 @@
/* 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.
*
*/
#if defined(__ANDROID__)
// Allow use of stuff in <time.h>
#define FORBIDDEN_SYMBOL_EXCEPTION_time_h
// Disable printf override in common/forbidden.h to avoid
// clashes with log.h from the Android SDK.
// That header file uses
// __attribute__ ((format(printf, 3, 4)))
// which gets messed up by our override mechanism; this could
// be avoided by either changing the Android SDK to use the equally
// legal and valid
// __attribute__ ((format(printf, 3, 4)))
// or by refining our printf override to use a varadic macro
// (which then wouldn't be portable, though).
// Anyway, for now we just disable the printf override globally
// for the Android port
#define FORBIDDEN_SYMBOL_EXCEPTION_printf
#include "base/main.h"
#include "graphics/surface.h"
#include "common/rect.h"
#include "common/array.h"
#include "common/util.h"
#include "common/tokenizer.h"
#include "backends/platform/android/texture.h"
#include "backends/platform/android/android.h"
// Supported GL extensions
static bool npot_supported = false;
#ifdef GL_OES_draw_texture
static bool draw_tex_supported = false;
#endif
static inline GLfixed xdiv(int numerator, int denominator) {
assert(numerator < (1 << 16));
return (numerator << 16) / denominator;
}
template<class T>
static T nextHigher2(T k) {
if (k == 0)
return 1;
--k;
for (uint i = 1; i < sizeof(T) * CHAR_BIT; i <<= 1)
k = k | k >> i;
return k + 1;
}
void GLESBaseTexture::initGLExtensions() {
const char *ext_string =
reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS));
LOGI("Extensions: %s", ext_string);
Common::StringTokenizer tokenizer(ext_string, " ");
while (!tokenizer.empty()) {
Common::String token = tokenizer.nextToken();
if (token == "GL_ARB_texture_non_power_of_two")
npot_supported = true;
#ifdef GL_OES_draw_texture
if (token == "GL_OES_draw_texture")
draw_tex_supported = true;
#endif
}
}
GLESBaseTexture::GLESBaseTexture(GLenum glFormat, GLenum glType,
Graphics::PixelFormat pixelFormat) :
_glFormat(glFormat),
_glType(glType),
_glFilter(GL_NEAREST),
_texture_name(0),
_surface(),
_texture_width(0),
_texture_height(0),
_draw_rect(),
_all_dirty(false),
_dirty_rect(),
_pixelFormat(pixelFormat),
_palettePixelFormat()
{
GLCALL(glGenTextures(1, &_texture_name));
}
GLESBaseTexture::~GLESBaseTexture() {
release();
}
void GLESBaseTexture::release() {
if (_texture_name) {
LOGD("Destroying texture %u", _texture_name);
GLCALL(glDeleteTextures(1, &_texture_name));
_texture_name = 0;
}
}
void GLESBaseTexture::reinit() {
GLCALL(glGenTextures(1, &_texture_name));
initSize();
setDirty();
}
void GLESBaseTexture::initSize() {
// Allocate room for the texture now, but pixel data gets uploaded
// later (perhaps with multiple TexSubImage2D operations).
GLCALL(glBindTexture(GL_TEXTURE_2D, _texture_name));
GLCALL(glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, _glFilter));
GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, _glFilter));
GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
GLCALL(glTexImage2D(GL_TEXTURE_2D, 0, _glFormat,
_texture_width, _texture_height,
0, _glFormat, _glType, 0));
}
void GLESBaseTexture::setLinearFilter(bool value) {
if (value)
_glFilter = GL_LINEAR;
else
_glFilter = GL_NEAREST;
GLCALL(glBindTexture(GL_TEXTURE_2D, _texture_name));
GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, _glFilter));
GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, _glFilter));
}
void GLESBaseTexture::allocBuffer(GLuint w, GLuint h) {
_surface.w = w;
_surface.h = h;
_surface.format = _pixelFormat;
if (w == _texture_width && h == _texture_height)
return;
if (npot_supported) {
_texture_width = _surface.w;
_texture_height = _surface.h;
} else {
_texture_width = nextHigher2(_surface.w);
_texture_height = nextHigher2(_surface.h);
}
initSize();
}
void GLESBaseTexture::drawTexture(GLshort x, GLshort y, GLshort w, GLshort h) {
GLCALL(glBindTexture(GL_TEXTURE_2D, _texture_name));
#ifdef GL_OES_draw_texture
// Great extension, but only works under specific conditions.
// Still a work-in-progress - disabled for now.
if (false && draw_tex_supported && !hasPalette()) {
//GLCALL(glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE));
const GLint crop[4] = { 0, _surface.h, _surface.w, -_surface.h };
GLCALL(glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop));
// Android GLES bug?
GLCALL(glColor4ub(0xff, 0xff, 0xff, 0xff));
GLCALL(glDrawTexiOES(x, y, 0, w, h));
} else
#endif
{
const GLfixed tex_width = xdiv(_surface.w, _texture_width);
const GLfixed tex_height = xdiv(_surface.h, _texture_height);
const GLfixed texcoords[] = {
0, 0,
tex_width, 0,
0, tex_height,
tex_width, tex_height,
};
GLCALL(glTexCoordPointer(2, GL_FIXED, 0, texcoords));
const GLshort vertices[] = {
x, y,
x + w, y,
x, y + h,
x + w, y + h,
};
GLCALL(glVertexPointer(2, GL_SHORT, 0, vertices));
assert(ARRAYSIZE(vertices) == ARRAYSIZE(texcoords));
GLCALL(glDrawArrays(GL_TRIANGLE_STRIP, 0, ARRAYSIZE(vertices) / 2));
}
clearDirty();
}
const Graphics::PixelFormat &GLESBaseTexture::getPixelFormat() const {
return _pixelFormat;
}
GLESTexture::GLESTexture(GLenum glFormat, GLenum glType,
Graphics::PixelFormat pixelFormat) :
GLESBaseTexture(glFormat, glType, pixelFormat),
_pixels(0),
_buf(0) {
}
GLESTexture::~GLESTexture() {
delete[] _buf;
delete[] _pixels;
}
void GLESTexture::allocBuffer(GLuint w, GLuint h) {
GLuint oldw = _surface.w;
GLuint oldh = _surface.h;
GLESBaseTexture::allocBuffer(w, h);
_surface.pitch = w * _pixelFormat.bytesPerPixel;
if (_surface.w == oldw && _surface.h == oldh) {
fillBuffer(0);
return;
}
delete[] _buf;
delete[] _pixels;
_pixels = new byte[w * h * _surface.format.bytesPerPixel];
assert(_pixels);
_surface.pixels = _pixels;
fillBuffer(0);
_buf = new byte[w * h * _surface.format.bytesPerPixel];
assert(_buf);
}
void GLESTexture::updateBuffer(GLuint x, GLuint y, GLuint w, GLuint h,
const void *buf, int pitch_buf) {
setDirtyRect(Common::Rect(x, y, x + w, y + h));
const byte *src = (const byte *)buf;
byte *dst = _pixels + y * _surface.pitch + x * _surface.format.bytesPerPixel;
do {
memcpy(dst, src, w * _surface.format.bytesPerPixel);
dst += _surface.pitch;
src += pitch_buf;
} while (--h);
}
void GLESTexture::fillBuffer(uint32 color) {
assert(_surface.pixels);
if (_pixelFormat.bytesPerPixel == 1 ||
((color & 0xff) == ((color >> 8) & 0xff)))
memset(_pixels, color & 0xff, _surface.pitch * _surface.h);
else
Common::fill(_pixels, _pixels + _surface.pitch * _surface.h,
(uint16)color);
setDirty();
}
void GLESTexture::drawTexture(GLshort x, GLshort y, GLshort w, GLshort h) {
if (_all_dirty) {
_dirty_rect.top = 0;
_dirty_rect.left = 0;
_dirty_rect.bottom = _surface.h;
_dirty_rect.right = _surface.w;
_all_dirty = false;
}
if (!_dirty_rect.isEmpty()) {
byte *_tex;
int16 dwidth = _dirty_rect.width();
int16 dheight = _dirty_rect.height();
if (dwidth == _surface.w) {
_tex = _pixels + _dirty_rect.top * _surface.pitch;
} else {
_tex = _buf;
byte *src = _pixels + _dirty_rect.top * _surface.pitch +
_dirty_rect.left * _surface.format.bytesPerPixel;
byte *dst = _buf;
uint16 l = dwidth * _surface.format.bytesPerPixel;
for (uint16 i = 0; i < dheight; ++i) {
memcpy(dst, src, l);
src += _surface.pitch;
dst += l;
}
}
GLCALL(glBindTexture(GL_TEXTURE_2D, _texture_name));
GLCALL(glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
GLCALL(glTexSubImage2D(GL_TEXTURE_2D, 0,
_dirty_rect.left, _dirty_rect.top,
dwidth, dheight, _glFormat, _glType, _tex));
}
GLESBaseTexture::drawTexture(x, y, w, h);
}
GLES4444Texture::GLES4444Texture() :
GLESTexture(GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, pixelFormat()) {
}
GLES4444Texture::~GLES4444Texture() {
}
GLES5551Texture::GLES5551Texture() :
GLESTexture(GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, pixelFormat()) {
}
GLES5551Texture::~GLES5551Texture() {
}
GLES565Texture::GLES565Texture() :
GLESTexture(GL_RGB, GL_UNSIGNED_SHORT_5_6_5, pixelFormat()) {
}
GLES565Texture::~GLES565Texture() {
}
GLESFakePaletteTexture::GLESFakePaletteTexture(GLenum glFormat, GLenum glType,
Graphics::PixelFormat pixelFormat) :
GLESBaseTexture(glFormat, glType, pixelFormat),
_palette(0),
_pixels(0),
_buf(0)
{
_palettePixelFormat = pixelFormat;
_fake_format = Graphics::PixelFormat::createFormatCLUT8();
_palette = new uint16[256];
assert(_palette);
memset(_palette, 0, 256 * 2);
}
GLESFakePaletteTexture::~GLESFakePaletteTexture() {
delete[] _buf;
delete[] _pixels;
delete[] _palette;
}
void GLESFakePaletteTexture::allocBuffer(GLuint w, GLuint h) {
GLuint oldw = _surface.w;
GLuint oldh = _surface.h;
GLESBaseTexture::allocBuffer(w, h);
_surface.format = Graphics::PixelFormat::createFormatCLUT8();
_surface.pitch = w;
if (_surface.w == oldw && _surface.h == oldh) {
fillBuffer(0);
return;
}
delete[] _buf;
delete[] _pixels;
_pixels = new byte[w * h];
assert(_pixels);
// fixup surface, for the outside this is a CLUT8 surface
_surface.pixels = _pixels;
fillBuffer(0);
_buf = new uint16[w * h];
assert(_buf);
}
void GLESFakePaletteTexture::fillBuffer(uint32 color) {
assert(_surface.pixels);
memset(_surface.pixels, color & 0xff, _surface.pitch * _surface.h);
setDirty();
}
void GLESFakePaletteTexture::updateBuffer(GLuint x, GLuint y, GLuint w,
GLuint h, const void *buf,
int pitch_buf) {
setDirtyRect(Common::Rect(x, y, x + w, y + h));
const byte *src = (const byte *)buf;
byte *dst = _pixels + y * _surface.pitch + x;
do {
memcpy(dst, src, w);
dst += _surface.pitch;
src += pitch_buf;
} while (--h);
}
void GLESFakePaletteTexture::drawTexture(GLshort x, GLshort y, GLshort w,
GLshort h) {
if (_all_dirty) {
_dirty_rect.top = 0;
_dirty_rect.left = 0;
_dirty_rect.bottom = _surface.h;
_dirty_rect.right = _surface.w;
_all_dirty = false;
}
if (!_dirty_rect.isEmpty()) {
int16 dwidth = _dirty_rect.width();
int16 dheight = _dirty_rect.height();
byte *src = _pixels + _dirty_rect.top * _surface.pitch +
_dirty_rect.left;
uint16 *dst = _buf;
uint pitch_delta = _surface.pitch - dwidth;
for (uint16 j = 0; j < dheight; ++j) {
for (uint16 i = 0; i < dwidth; ++i)
*dst++ = _palette[*src++];
src += pitch_delta;
}
GLCALL(glBindTexture(GL_TEXTURE_2D, _texture_name));
GLCALL(glTexSubImage2D(GL_TEXTURE_2D, 0,
_dirty_rect.left, _dirty_rect.top,
dwidth, dheight, _glFormat, _glType, _buf));
}
GLESBaseTexture::drawTexture(x, y, w, h);
}
const Graphics::PixelFormat &GLESFakePaletteTexture::getPixelFormat() const {
return _fake_format;
}
GLESFakePalette565Texture::GLESFakePalette565Texture() :
GLESFakePaletteTexture(GL_RGB, GL_UNSIGNED_SHORT_5_6_5,
GLES565Texture::pixelFormat()) {
}
GLESFakePalette565Texture::~GLESFakePalette565Texture() {
}
GLESFakePalette5551Texture::GLESFakePalette5551Texture() :
GLESFakePaletteTexture(GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1,
GLES5551Texture::pixelFormat()) {
}
GLESFakePalette5551Texture::~GLESFakePalette5551Texture() {
}
#endif

View File

@ -0,0 +1,274 @@
/* 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.
*
*/
#ifndef _ANDROID_TEXTURE_H_
#define _ANDROID_TEXTURE_H_
#if defined(__ANDROID__)
#define GL_GLEXT_PROTOTYPES
#include <GLES/gl.h>
#include "graphics/surface.h"
#include "graphics/pixelformat.h"
#include "common/rect.h"
#include "common/array.h"
class GLESBaseTexture {
public:
static void initGLExtensions();
protected:
GLESBaseTexture(GLenum glFormat, GLenum glType,
Graphics::PixelFormat pixelFormat);
public:
virtual ~GLESBaseTexture();
void release();
void reinit();
void initSize();
void setLinearFilter(bool value);
virtual void allocBuffer(GLuint w, GLuint h);
virtual void updateBuffer(GLuint x, GLuint y, GLuint width, GLuint height,
const void *buf, int pitch_buf) = 0;
virtual void fillBuffer(uint32 color) = 0;
virtual void drawTexture(GLshort x, GLshort y, GLshort w, GLshort h);
inline void setDrawRect(const Common::Rect &rect) {
_draw_rect = rect;
}
inline void setDrawRect(int16 w, int16 h) {
_draw_rect = Common::Rect(w, h);
}
inline void setDrawRect(int16 x1, int16 y1, int16 x2, int16 y2) {
_draw_rect = Common::Rect(x1, y1, x2, y2);
}
inline const Common::Rect &getDrawRect() const {
return _draw_rect;
}
inline void drawTextureRect() {
drawTexture(_draw_rect.left, _draw_rect.top,
_draw_rect.width(), _draw_rect.height());
}
inline void drawTextureOrigin() {
drawTexture(0, 0, _surface.w, _surface.h);
}
inline GLuint width() const {
return _surface.w;
}
inline GLuint height() const {
return _surface.h;
}
inline uint16 pitch() const {
return _surface.pitch;
}
inline bool isEmpty() const {
return _surface.w == 0 || _surface.h == 0;
}
inline const Graphics::Surface *surface_const() const {
return &_surface;
}
inline Graphics::Surface *surface() {
setDirty();
return &_surface;
}
virtual const byte *palette_const() const {
return 0;
};
virtual byte *palette() {
return 0;
};
inline bool hasPalette() const {
return _palettePixelFormat.bytesPerPixel > 0;
}
inline bool dirty() const {
return _all_dirty || !_dirty_rect.isEmpty();
}
virtual const Graphics::PixelFormat &getPixelFormat() const;
inline const Graphics::PixelFormat &getPalettePixelFormat() const {
return _palettePixelFormat;
}
protected:
inline void setDirty() {
_all_dirty = true;
}
inline void clearDirty() {
_all_dirty = false;
_dirty_rect.top = 0;
_dirty_rect.left = 0;
_dirty_rect.bottom = 0;
_dirty_rect.right = 0;
}
inline void setDirtyRect(const Common::Rect& r) {
if (!_all_dirty) {
if (_dirty_rect.isEmpty())
_dirty_rect = r;
else
_dirty_rect.extend(r);
}
}
GLenum _glFormat;
GLenum _glType;
GLint _glFilter;
GLuint _texture_name;
Graphics::Surface _surface;
GLuint _texture_width;
GLuint _texture_height;
Common::Rect _draw_rect;
bool _all_dirty;
Common::Rect _dirty_rect;
Graphics::PixelFormat _pixelFormat;
Graphics::PixelFormat _palettePixelFormat;
};
class GLESTexture : public GLESBaseTexture {
protected:
GLESTexture(GLenum glFormat, GLenum glType,
Graphics::PixelFormat pixelFormat);
public:
virtual ~GLESTexture();
virtual void allocBuffer(GLuint w, GLuint h);
virtual void updateBuffer(GLuint x, GLuint y, GLuint width, GLuint height,
const void *buf, int pitch_buf);
virtual void fillBuffer(uint32 color);
virtual void drawTexture(GLshort x, GLshort y, GLshort w, GLshort h);
protected:
byte *_pixels;
byte *_buf;
};
// RGBA4444 texture
class GLES4444Texture : public GLESTexture {
public:
GLES4444Texture();
virtual ~GLES4444Texture();
static Graphics::PixelFormat pixelFormat() {
return Graphics::PixelFormat(2, 4, 4, 4, 4, 12, 8, 4, 0);
}
};
// RGBA5551 texture
class GLES5551Texture : public GLESTexture {
public:
GLES5551Texture();
virtual ~GLES5551Texture();
static inline Graphics::PixelFormat pixelFormat() {
return Graphics::PixelFormat(2, 5, 5, 5, 1, 11, 6, 1, 0);
}
};
// RGB565 texture
class GLES565Texture : public GLESTexture {
public:
GLES565Texture();
virtual ~GLES565Texture();
static inline Graphics::PixelFormat pixelFormat() {
return Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0);
}
};
class GLESFakePaletteTexture : public GLESBaseTexture {
protected:
GLESFakePaletteTexture(GLenum glFormat, GLenum glType,
Graphics::PixelFormat pixelFormat);
public:
virtual ~GLESFakePaletteTexture();
virtual void allocBuffer(GLuint w, GLuint h);
virtual void updateBuffer(GLuint x, GLuint y, GLuint width, GLuint height,
const void *buf, int pitch_buf);
virtual void fillBuffer(uint32 color);
virtual void drawTexture(GLshort x, GLshort y, GLshort w, GLshort h);
virtual const byte *palette_const() const {
return (byte *)_palette;
};
virtual byte *palette() {
setDirty();
return (byte *)_palette;
};
virtual const Graphics::PixelFormat &getPixelFormat() const;
protected:
Graphics::PixelFormat _fake_format;
uint16 *_palette;
byte *_pixels;
uint16 *_buf;
};
class GLESFakePalette565Texture : public GLESFakePaletteTexture {
public:
GLESFakePalette565Texture();
virtual ~GLESFakePalette565Texture();
};
class GLESFakePalette5551Texture : public GLESFakePaletteTexture {
public:
GLESFakePalette5551Texture();
virtual ~GLESFakePalette5551Texture();
};
#endif
#endif

View File

@ -121,14 +121,14 @@ public:
#ifdef USE_MT32EMU
LINK_PLUGIN(MT32)
#endif
#if defined(__ANDROID__)
LINK_PLUGIN(EAS)
#endif
LINK_PLUGIN(ADLIB)
//ResidualVM: disabled belows
// LINK_PLUGIN(PCSPK)
// LINK_PLUGIN(PCJR)
LINK_PLUGIN(CMS)
#if defined(__ANDROID__)
// LINK_PLUGIN(EAS)
#endif
#ifndef DISABLE_SID
// LINK_PLUGIN(C64)
#endif

11
configure vendored
View File

@ -716,7 +716,7 @@ Usage: $0 [OPTIONS]...
Configuration:
-h, --help display this help and exit
--backend=BACKEND backend to build (sdl, null) [sdl]
--backend=BACKEND backend to build (android, sdl, null) [sdl]
Installation directories:
--prefix=PREFIX install architecture-independent files in PREFIX
@ -742,6 +742,7 @@ Fine tuning of the installation directories:
Special configuration feature:
--host=HOST cross-compile to target HOST (arm-linux, ...)
special targets: android for Android
Game engines:
--enable-all-engines enable all engines, including those which are
@ -1854,15 +1855,17 @@ case $_host_os in
CXXFLAGS="$CXXFLAGS -march=armv5te"
CXXFLAGS="$CXXFLAGS -mtune=xscale"
CXXFLAGS="$CXXFLAGS -msoft-float"
LDFLAGS="$LDFLAGS -L$ANDROID_NDK/sources/cxx-stl/gnu-libstdc++/libs/armeabi"
;;
android-v7a)
CXXFLAGS="$CXXFLAGS -march=armv7-a"
CXXFLAGS="$CXXFLAGS -mfloat-abi=softfp"
CXXFLAGS="$CXXFLAGS -mfpu=vfp"
LDFLAGS="$LDFLAGS -Wl,--fix-cortex-a8"
LDFLAGS="$LDFLAGS -L$ANDROID_NDK/sources/cxx-stl/gnu-libstdc++/libs/armeabi-v7a"
;;
esac
CXXFLAGS="$CXXFLAGS --sysroot=$ANDROID_NDK/platforms/android-4/arch-arm"
CXXFLAGS="$CXXFLAGS --sysroot=$ANDROID_NDK/platforms/android-5/arch-arm"
CXXFLAGS="$CXXFLAGS -fpic"
CXXFLAGS="$CXXFLAGS -ffunction-sections"
CXXFLAGS="$CXXFLAGS -funwind-tables"
@ -1883,10 +1886,12 @@ case $_host_os in
CXXFLAGS="$CXXFLAGS -D__ARM_ARCH_5TE__"
# supress 'mangling of 'va_list' has changed in GCC 4.4'
CXXFLAGS="$CXXFLAGS -Wno-psabi"
LDFLAGS="$LDFLAGS --sysroot=$ANDROID_NDK/platforms/android-4/arch-arm"
LDFLAGS="$LDFLAGS --sysroot=$ANDROID_NDK/platforms/android-5/arch-arm"
LDFLAGS="$LDFLAGS -mthumb-interwork"
add_line_to_config_mk "ANDROID_SDK = $ANDROID_SDK"
_seq_midi=no
# for RTTI (dynamic_cast, ...) support
LIBS="-lsupc++"
;;
bada)
BADA_SDK_ROOT="`cygpath -m ${BADA_SDK}`"

View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- NB: android:versionCode needs to be bumped for formal releases -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.residualvm.residualvm"
android:versionCode="@ANDROID_VERSIONCODE@"
android:versionName="0.1.0git"
android:installLocation="preferExternal"
android:sharedUserId="org.residualvm.residualvm">
<!-- This version works on Android 1.5 (SDK 3) and newer, but we
want Android 2.2 (SDK 8) defaults and features. -->
<uses-sdk android:minSdkVersion="5"
android:targetSdkVersion="8"/>
<application android:name=".ResidualVMApplication"
android:label="@string/app_name"
android:description="@string/app_desc"
android:debuggable="true"
android:icon="@drawable/residualvm">
<activity android:name=".ResidualVMActivity"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:screenOrientation="landscape"
android:configChanges="orientation|keyboardHidden"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
</activity>
<activity android:name=".Unpacker"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:screenOrientation="landscape"
android:configChanges="orientation|keyboardHidden">
<meta-data android:name="org.residualvm.unpacker.nextActivity"
android:value="org.residualvm.residualvm/.ResidualVMActivity"/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
<permission android:name="org.residualvm.residualvm.permission.RESIDUALVM_PLUGIN"
android:label="@string/residualvm_perm_plugin_label"
android:description="@string/residualvm_perm_plugin_desc"
android:protectionLevel="signature"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- Always needs some sort of qwerty keyboard.
Can work with a D-pad / trackball -->
<uses-configuration android:reqFiveWayNav="true"
android:reqKeyboardType="qwerty"/>
<!-- .. or touchscreen -->
<uses-configuration android:reqTouchScreen="finger"
android:reqKeyboardType="qwerty"/>
<uses-configuration android:reqTouchScreen="stylus"
android:reqKeyboardType="qwerty"/>
</manifest>

View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- NB: android:versionCode needs to be bumped for formal releases -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.residualvm.residualvm"
android:versionCode="@ANDROID_VERSIONCODE@"
android:versionName="@VERSION@"
android:installLocation="preferExternal"
android:sharedUserId="org.residualvm.residualvm">
<!-- This version works on Android 1.5 (SDK 3) and newer, but we
want Android 2.2 (SDK 8) defaults and features. -->
<uses-sdk android:minSdkVersion="5"
android:targetSdkVersion="8"/>
<application android:name=".ResidualVMApplication"
android:label="@string/app_name"
android:description="@string/app_desc"
android:icon="@drawable/residualvm">
<activity android:name=".ResidualVMActivity"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:screenOrientation="landscape"
android:configChanges="orientation|keyboardHidden"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
</activity>
<activity android:name=".Unpacker"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:screenOrientation="landscape"
android:configChanges="orientation|keyboardHidden">
<meta-data android:name="org.residualvm.unpacker.nextActivity"
android:value="org.residualvm.residualvm/.ResidualVMActivity"/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
<permission android:name="org.residualvm.residualvm.permission.RESIDUALVM_PLUGIN"
android:label="@string/residualvm_perm_plugin_label"
android:description="@string/residualvm_perm_plugin_desc"
android:protectionLevel="signature"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- Always needs some sort of qwerty keyboard.
Can work with a D-pad / trackball -->
<uses-configuration android:reqFiveWayNav="true"
android:reqKeyboardType="qwerty"/>
<!-- .. or touchscreen -->
<uses-configuration android:reqTouchScreen="finger"
android:reqKeyboardType="qwerty"/>
<uses-configuration android:reqTouchScreen="stylus"
android:reqKeyboardType="qwerty"/>
</manifest>

View File

@ -0,0 +1,55 @@
README for the Android port of ResidualVM
--------------------------------------
REQUIREMENTS
TODO
INSTALL
TODO
CONTROLS
5-Way navigation control / DPAD
DPAD up/down/left/right: Mouse movement
DPAD center: Left mouse button
Trackball
Movement: Mouse movement
Click: Left mouse button
Touchscreen
The touchscreen can be used in two modes
1) Direct mode
2) Touchpad mode
When in direct mode, the mouse cursor moves to the touched point on screen.
In touchpad mode, the mouse cursor is independent of the touched point, it
is moved relative to its current position - like on a touchpad.
The port currently misses its own configuration dialog, the mode can
be toggled with the "Mixed AdLib/MIDI mode" on the MIDI tab in ResidualVM's
own option dialog.
Tap + movement: Mouse movement
Tap without movement: Left mouse button click
Tap held for >0.5s without movement: Right mouse button click
Tap held for >1s without movement: Middle mouse button click
Double Tap + movement: Drag and drop
On devices supporting multitouch:
Two finger tap: Right mouse button click
Three finger tap: Middle mouse button click
System keys
Back button: Escape
Menu button: ResidualVM menu
Menu button held for 0.5s: Toggle virtual keyboard
Camera or Search button: Right mouse button click

16
dists/android/mkplugin.sh Executable file
View File

@ -0,0 +1,16 @@
#!/bin/sh
if [ $# -ne 5 ]; then
echo "usage: $0 configure plugin template versioncode target"
exit 1
fi
CONFIGURE=$1
PLUGIN_NAME=$2
TEMPLATE=$3
PLUGIN_VERSION_CODE=$4
TARGET=$5
PLUGIN_DESC=`sed -n "s/add_engine\s$PLUGIN_NAME\s\"\(.\+\)\"\s.*/\1/p" < $CONFIGURE`
sed "s|@PLUGIN_NAME@|$PLUGIN_NAME|;s|@PLUGIN_VERSION_CODE@|$PLUGIN_VERSION_CODE|;s|@PLUGIN_DESC@|$PLUGIN_DESC|" < $TEMPLATE > $TARGET

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.residualvm.residualvm.plugin.@PLUGIN_NAME@"
android:versionCode="@PLUGIN_VERSION_CODE@"
android:versionName="1.5.0git"
android:installLocation="preferExternal"
android:sharedUserId="org.residualvm.residualvm">
<uses-sdk android:minSdkVersion="5" android:targetSdkVersion="8" />
<application android:label="@string/app_name"
android:description="@string/app_desc"
android:icon="@drawable/residualvm">
<receiver android:name="org.residualvm.residualvm.PluginProvider"
android:process="org.residualvm.residualvm">
<intent-filter>
<action android:name="org.residualvm.residualvm.action.PLUGIN_QUERY"/>
<category android:name="android.intent.category.INFO"/>
</intent-filter>
<meta-data android:name="org.residualvm.residualvm.meta.UNPACK_LIB"
android:value="mylib/armeabi/lib@PLUGIN_NAME@.so" />
</receiver>
</application>
<uses-permission android:name="org.residualvm.residualvm.permission.RESIDUALVM_PLUGIN"/>
<uses-configuration android:reqFiveWayNav="true"
android:reqKeyboardType="qwerty"/>
<uses-configuration android:reqTouchScreen="finger"
android:reqKeyboardType="qwerty"/>
<uses-configuration android:reqTouchScreen="stylus"
android:reqKeyboardType="qwerty"/>
</manifest>

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.residualvm.residualvm.plugin.@PLUGIN_NAME@"
android:versionCode="@PLUGIN_VERSION_CODE@"
android:versionName="@VERSION@"
android:installLocation="preferExternal"
android:sharedUserId="org.residualvm.residualvm">
<uses-sdk android:minSdkVersion="5" android:targetSdkVersion="8" />
<application android:label="@string/app_name"
android:description="@string/app_desc"
android:icon="@drawable/residualvm">
<receiver android:name="org.residualvm.residualvm.PluginProvider"
android:process="org.residualvm.residualvm">
<intent-filter>
<action android:name="org.residualvm.residualvm.action.PLUGIN_QUERY"/>
<category android:name="android.intent.category.INFO"/>
</intent-filter>
<meta-data android:name="org.residualvm.residualvm.meta.UNPACK_LIB"
android:value="mylib/armeabi/lib@PLUGIN_NAME@.so" />
</receiver>
</application>
<uses-permission android:name="org.residualvm.residualvm.permission.RESIDUALVM_PLUGIN"/>
<uses-configuration android:reqFiveWayNav="true"
android:reqKeyboardType="qwerty"/>
<uses-configuration android:reqTouchScreen="finger"
android:reqKeyboardType="qwerty"/>
<uses-configuration android:reqTouchScreen="stylus"
android:reqKeyboardType="qwerty"/>
</manifest>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">ResidualVM plugin: "@PLUGIN_NAME@"</string>
<string name="app_desc">Game engine for: @PLUGIN_DESC@</string>
</resources>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="#4889a7"
android:endColor="#1d7095"
android:angle="315" />
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<org.residualvm.residualvm.EditableSurfaceView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_surface"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:keepScreenOn="true"
android:focusable="true"
android:focusableInTouchMode="true"
/>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:gravity="center"
android:background="@drawable/gradient"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:src="@drawable/residualvm_big" />
<ProgressBar android:id="@+id/progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="300dip"
android:layout_height="wrap_content"
android:padding="20dip"/>
</LinearLayout>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">ResidualVM</string>
<string name="app_desc">3D adventure game interpreter</string>
<string name="quit">Quit</string>
<string name="residualvm_perm_plugin_label">ResidualVM plugin</string>
<string name="residualvm_perm_plugin_desc">Allows the application to
provide a ResidualVM loadable plugin: code that will be executed in the
ResidualVM application. Malicious plugins may do anything ResidualVM
itself could do: write to your SD card, delete your savegames,
change the ResidualVM background to puce, replace menu labels with rude
words, etc.</string>
<string name="no_sdcard_title">No SD card?</string>
<string name="no_sdcard">Unable to read your SD card. This usually
means you still have it mounted on your PC. Unmount, reinsert,
whatever and then try again.</string>
<string name="no_plugins_title">No plugins found</string>
<string name="no_plugins_found">ResidualVM requires at least one <i>game
engine</i> to be useful. Engines are available as separate plugin
packages, from wherever you found ResidualVM.</string>
<string name="to_market">To Market</string>
</resources>

View File

@ -277,7 +277,7 @@ public:
/**
* Run the Global Main Menu Dialog
*/
void openMainMenuDialog();
virtual void openMainMenuDialog();
/**
* Display a warning to the user that the game is not fully supported.

View File

@ -1134,4 +1134,10 @@ bool GrimEngine::hasFeature(EngineFeature f) const {
(f == kSupportsLoadingDuringRuntime);
}
void GrimEngine::openMainMenuDialog() {
Common::KeyState key(Common::KEYCODE_F1, Common::ASCII_F1);
handleControls(Common::EVENT_KEYDOWN, key);
handleControls(Common::EVENT_KEYUP, key);
}
} // end of namespace Grim

View File

@ -179,6 +179,8 @@ public:
TextObjectDefaults _sayLineDefaults, _printLineDefaults, _blastTextDefaults;
virtual void openMainMenuDialog();
private:
void handleControls(Common::EventType type, const Common::KeyState &key);
void handleChars(Common::EventType type, const Common::KeyState &key);

149
graphics/conversion.cpp Normal file
View File

@ -0,0 +1,149 @@
/* 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.
*/
#include "graphics/conversion.h"
#include "graphics/pixelformat.h"
namespace Graphics {
// TODO: YUV to RGB conversion function
// Function to blit a rect from one color format to another
bool crossBlit(byte *dst, const byte *src, int dstpitch, int srcpitch,
int w, int h, const Graphics::PixelFormat &dstFmt, const Graphics::PixelFormat &srcFmt) {
// Error out if conversion is impossible
if ((srcFmt.bytesPerPixel == 1) || (dstFmt.bytesPerPixel == 1)
|| (!srcFmt.bytesPerPixel) || (!dstFmt.bytesPerPixel)
|| (srcFmt.bytesPerPixel > dstFmt.bytesPerPixel))
return false;
// Don't perform unnecessary conversion
if (srcFmt == dstFmt) {
if (dst == src)
return true;
if (dstpitch == srcpitch && ((w * dstFmt.bytesPerPixel) == dstpitch)) {
memcpy(dst,src,dstpitch * h);
return true;
} else {
for (int i = 0; i < h; i++) {
memcpy(dst,src,w * dstFmt.bytesPerPixel);
dst += dstpitch;
src += srcpitch;
}
return true;
}
}
// Faster, but larger, to provide optimized handling for each case.
int srcDelta, dstDelta;
srcDelta = (srcpitch - w * srcFmt.bytesPerPixel);
dstDelta = (dstpitch - w * dstFmt.bytesPerPixel);
// TODO: optimized cases for dstDelta of 0
uint8 r, g, b, a;
if (dstFmt.bytesPerPixel == 2) {
uint16 color;
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++, src += 2, dst += 2) {
color = *(const uint16 *)src;
srcFmt.colorToARGB(color, a, r, g, b);
color = dstFmt.ARGBToColor(a, r, g, b);
*(uint16 *)dst = color;
}
src += srcDelta;
dst += dstDelta;
}
} else if (dstFmt.bytesPerPixel == 3) {
uint32 color;
uint8 *col = (uint8 *) &color;
#ifdef SCUMM_BIG_ENDIAN
col++;
#endif
if (srcFmt.bytesPerPixel == 2) {
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++, src += 2, dst += 3) {
color = *(const uint16 *)src;
srcFmt.colorToARGB(color, a, r, g, b);
color = dstFmt.ARGBToColor(a, r, g, b);
memcpy(dst, col, 3);
}
src += srcDelta;
dst += dstDelta;
}
} else {
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++, src += 3, dst += 3) {
memcpy(col, src, 3);
srcFmt.colorToARGB(color, a, r, g, b);
color = dstFmt.ARGBToColor(a, r, g, b);
memcpy(dst, col, 3);
}
src += srcDelta;
dst += dstDelta;
}
}
} else if (dstFmt.bytesPerPixel == 4) {
uint32 color;
if (srcFmt.bytesPerPixel == 2) {
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++, src += 2, dst += 4) {
color = *(const uint16 *)src;
srcFmt.colorToARGB(color, a, r, g, b);
color = dstFmt.ARGBToColor(a, r, g, b);
*(uint32 *)dst = color;
}
src += srcDelta;
dst += dstDelta;
}
} else if (srcFmt.bytesPerPixel == 3) {
uint8 *col = (uint8 *)&color;
#ifdef SCUMM_BIG_ENDIAN
col++;
#endif
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++, src += 2, dst += 4) {
memcpy(col, src, 3);
srcFmt.colorToARGB(color, a, r, g, b);
color = dstFmt.ARGBToColor(a, r, g, b);
*(uint32 *)dst = color;
}
src += srcDelta;
dst += dstDelta;
}
} else {
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++, src += 4, dst += 4) {
color = *(const uint32 *)src;
srcFmt.colorToARGB(color, a, r, g, b);
color = dstFmt.ARGBToColor(a, r, g, b);
*(uint32 *)dst = color;
}
src += srcDelta;
dst += dstDelta;
}
}
} else {
return false;
}
return true;
}
} // End of namespace Graphics

View File

@ -1,6 +1,7 @@
MODULE := graphics
MODULE_OBJS := \
conversion.o \
cursorman.o \
font.o \
fontman.o \