mirror of
https://github.com/libretro/scummvm.git
synced 2025-02-04 18:06:26 +00:00
Merge pull request #613 from tobigun/android
ANDROID: preliminary Android port for ResidualVM
This commit is contained in:
commit
308f407969
613
backends/platform/android/android.cpp
Normal file
613
backends/platform/android/android.cpp
Normal 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
|
317
backends/platform/android/android.h
Normal file
317
backends/platform/android/android.h
Normal 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
|
202
backends/platform/android/android.mk
Normal file
202
backends/platform/android/android.mk
Normal 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
|
504
backends/platform/android/asset-archive.cpp
Normal file
504
backends/platform/android/asset-archive.cpp
Normal 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
|
54
backends/platform/android/asset-archive.h
Normal file
54
backends/platform/android/asset-archive.h
Normal 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
|
1021
backends/platform/android/events.cpp
Normal file
1021
backends/platform/android/events.cpp
Normal file
File diff suppressed because it is too large
Load Diff
886
backends/platform/android/gfx.cpp
Normal file
886
backends/platform/android/gfx.cpp
Normal 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
|
621
backends/platform/android/jni.cpp
Normal file
621
backends/platform/android/jni.cpp
Normal 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
|
147
backends/platform/android/jni.h
Normal file
147
backends/platform/android/jni.h
Normal 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
|
14
backends/platform/android/module.mk
Normal file
14
backends/platform/android/module.mk
Normal 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)))
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
490
backends/platform/android/texture.cpp
Normal file
490
backends/platform/android/texture.cpp
Normal 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
|
274
backends/platform/android/texture.h
Normal file
274
backends/platform/android/texture.h
Normal 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
|
@ -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
11
configure
vendored
@ -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}`"
|
||||
|
62
dists/android/AndroidManifest.xml
Normal file
62
dists/android/AndroidManifest.xml
Normal 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>
|
61
dists/android/AndroidManifest.xml.in
Normal file
61
dists/android/AndroidManifest.xml.in
Normal 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>
|
55
dists/android/README.Android
Normal file
55
dists/android/README.Android
Normal 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
16
dists/android/mkplugin.sh
Executable 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
|
35
dists/android/plugin-manifest.xml
Normal file
35
dists/android/plugin-manifest.xml
Normal 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>
|
35
dists/android/plugin-manifest.xml.in
Normal file
35
dists/android/plugin-manifest.xml.in
Normal 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>
|
6
dists/android/plugin-strings.xml
Normal file
6
dists/android/plugin-strings.xml
Normal 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>
|
7
dists/android/res/drawable/gradient.xml
Normal file
7
dists/android/res/drawable/gradient.xml
Normal 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>
|
BIN
dists/android/res/drawable/residualvm.png
Normal file
BIN
dists/android/res/drawable/residualvm.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.5 KiB |
BIN
dists/android/res/drawable/residualvm_big.png
Normal file
BIN
dists/android/res/drawable/residualvm_big.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
12
dists/android/res/layout/main.xml
Normal file
12
dists/android/res/layout/main.xml
Normal 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"
|
||||
/>
|
19
dists/android/res/layout/splash.xml
Normal file
19
dists/android/res/layout/splash.xml
Normal 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>
|
22
dists/android/res/values/strings.xml
Normal file
22
dists/android/res/values/strings.xml
Normal 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>
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
149
graphics/conversion.cpp
Normal 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
|
@ -1,6 +1,7 @@
|
||||
MODULE := graphics
|
||||
|
||||
MODULE_OBJS := \
|
||||
conversion.o \
|
||||
cursorman.o \
|
||||
font.o \
|
||||
fontman.o \
|
||||
|
Loading…
x
Reference in New Issue
Block a user