parent
3efec5720d
commit
46155b2c36
24 changed files with 4121 additions and 7 deletions
84
backends/platform/android/README.build
Normal file
84
backends/platform/android/README.build
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
Building the ScummVM Android port
|
||||||
|
=================================
|
||||||
|
|
||||||
|
You will need these things to build:
|
||||||
|
1. Android EGL headers and library
|
||||||
|
2. Android SDK
|
||||||
|
3. An arm-android-eabi GCC toolchain
|
||||||
|
|
||||||
|
In the example commands, we are going to build against the Android 1.5
|
||||||
|
native ABI (but using the Android 1.6 SDK tools). Other version
|
||||||
|
combinations might/should be possible with a bit of tweaking.
|
||||||
|
|
||||||
|
In detail:
|
||||||
|
|
||||||
|
1. Android EGL headers and library
|
||||||
|
|
||||||
|
You can build these from the full Android source, but it is far easier
|
||||||
|
to just download the 3 Android EGL headers from here:
|
||||||
|
http://android.git.kernel.org/?p=platform/frameworks/base.git;a=tree;f=opengl/include/EGL;hb=HEAD
|
||||||
|
(copy them to a directory called "EGL" somewhere)
|
||||||
|
|
||||||
|
... and grab libEGL.so off an existing phone/emulator:
|
||||||
|
adb pull /system/lib/libEGL.so /tmp
|
||||||
|
|
||||||
|
2. Android SDK
|
||||||
|
|
||||||
|
Download and install somewhere.
|
||||||
|
|
||||||
|
3. arm-android-eabi GCC toolchain
|
||||||
|
|
||||||
|
You have several choices for toolchains:
|
||||||
|
|
||||||
|
- Use Google arm-eabi prebuilt toolchain.
|
||||||
|
|
||||||
|
This is shipped with both the Android source release and Android NDK.
|
||||||
|
The problem is that "arm-eabi-gcc" can't actually link anything
|
||||||
|
successfully without extra command line flags. To use this with the
|
||||||
|
ScummVM configure/build environment you will need to create a family
|
||||||
|
of shell wrapper scripts that convert "arm-android-eabi-foo" to
|
||||||
|
"arm-eabi-foo -mandroid".
|
||||||
|
|
||||||
|
For example, I use this script:
|
||||||
|
#!/bin/sh
|
||||||
|
exec arm-eabi-${0##*-} -mandroid -DANDROID "$@"
|
||||||
|
|
||||||
|
... and create a family of symlinks/hardlinks pointing to it called
|
||||||
|
arm-android-eabi-gcc, arm-android-eabi-g++, etc. For tools that don't
|
||||||
|
take a "-mandroid" argument - like arm-eabi-strip - I bypass the shell
|
||||||
|
wrapper and just create an arm-android-eabi-strip symlink to the tool
|
||||||
|
directly.
|
||||||
|
|
||||||
|
- Build your own arm-android-eabi toolchain from GCC source.
|
||||||
|
|
||||||
|
This is lots of fun. I suggest my Android openembedded patches, see:
|
||||||
|
http://wiki.github.com/anguslees/openembedded-android/
|
||||||
|
(You just need to have lots of disk space and type a few commands)
|
||||||
|
If you get stuck, ask
|
||||||
|
|
||||||
|
Alternatively, do a websearch - there are several other cross-compile
|
||||||
|
toolchains around.
|
||||||
|
|
||||||
|
|
||||||
|
Building ScummVM
|
||||||
|
================
|
||||||
|
|
||||||
|
export ANDROID_SDK=<root of Android SDK>
|
||||||
|
|
||||||
|
PATH=$ANDROID_SDK/platforms/android-1.6/tools:$ANDROID_SDK/tools:$PATH
|
||||||
|
# You also want to ensure your arm-android-eabi toolchain is in your $PATH
|
||||||
|
|
||||||
|
export ANDROID_TOP=<root of built Android source>
|
||||||
|
|
||||||
|
EGL_INC="-I<location of EGL/ header directory>"
|
||||||
|
EGL_LIBS="-L<location of libEGL.so>"
|
||||||
|
|
||||||
|
CPPFLAGS="$EGL_INC" \
|
||||||
|
LDFLAGS="-g $EGL_LIBS" \
|
||||||
|
./configure --backend=android --host=android --enable-zlib #and any other flags
|
||||||
|
make scummvm.apk
|
||||||
|
|
||||||
|
This will build a "monolithic" ScummVM package, with the engines
|
||||||
|
statically linked in. If you want to build separate engine packages,
|
||||||
|
like on the market, add "--enable-plugins --default-dynamic" to
|
||||||
|
configure and also make scummvm-engine-scumm.apk, etc.
|
1413
backends/platform/android/android.cpp
Normal file
1413
backends/platform/android/android.cpp
Normal file
File diff suppressed because it is too large
Load diff
52
backends/platform/android/android.mk
Normal file
52
backends/platform/android/android.mk
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
# Android specific build targets
|
||||||
|
|
||||||
|
AAPT = aapt
|
||||||
|
DX = dx
|
||||||
|
APKBUILDER = apkbuilder
|
||||||
|
ADB = adb -e
|
||||||
|
ANDROID_JAR = $(ANDROID_SDK)/platforms/android-1.6/android.jar
|
||||||
|
JAVAC ?= javac
|
||||||
|
JAVACFLAGS = -source 1.5 -target 1.5
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
scummvm.apk: build.tmp/libscummvm.so resources.ap_ classes.dex
|
||||||
|
# Package installer won't delete old libscummvm.so on upgrade so
|
||||||
|
# replace it with a zero size file
|
||||||
|
$(INSTALL) -d build.stage/common/lib/armeabi
|
||||||
|
touch build.stage/common/lib/armeabi/libscummvm.so
|
||||||
|
# We now handle the library unpacking ourselves from mylib/
|
||||||
|
$(INSTALL) -d build.stage/common/mylib/armeabi
|
||||||
|
$(INSTALL) -c -m 644 build.tmp/libscummvm.so build.stage/common/mylib/armeabi/
|
||||||
|
$(STRIP) build.stage/common/mylib/armeabi/libscummvm.so
|
||||||
|
# "-nf lib/armeabi/libscummvm.so" builds bogus paths?
|
||||||
|
$(APKBUILDER) $@ -z resources.ap_ -f classes.dex -rf build.stage/common || { $(RM) $@; exit 1; }
|
||||||
|
|
||||||
|
scummvm-engine-%.apk: plugins/lib%.so build.tmp/%/resources.ap_ build.tmp/plugins/classes.dex
|
||||||
|
$(INSTALL) -d build.stage/$*/apk/mylib/armeabi/
|
||||||
|
$(INSTALL) -c -m 644 plugins/lib$*.so build.stage/$*/apk/mylib/armeabi/
|
||||||
|
$(STRIP) build.stage/$*/apk/mylib/armeabi/lib$*.so
|
||||||
|
$(APKBUILDER) $@ -z build.tmp/$*/resources.ap_ -f build.tmp/plugins/classes.dex -rf build.stage/$*/apk || { $(RM) $@; exit 1; }
|
||||||
|
|
||||||
|
release/%.apk: %.apk
|
||||||
|
@$(MKDIR) -p $(@D)
|
||||||
|
@$(RM) $@
|
||||||
|
$(CP) $< $@.tmp
|
||||||
|
# remove debugging signature
|
||||||
|
zip -d $@.tmp META-INF/\*
|
||||||
|
jarsigner $(JARSIGNER_FLAGS) $@.tmp release
|
||||||
|
zipalign 4 $@.tmp $@
|
||||||
|
$(RM) $@.tmp
|
||||||
|
|
||||||
|
androidrelease: release/scummvm.apk $(patsubst plugins/lib%.so,release/scummvm-engine-%.apk,$(PLUGINS))
|
||||||
|
|
||||||
|
androidtest: scummvm.apk scummvm-engine-scumm.apk scummvm-engine-kyra.apk
|
||||||
|
@set -e; for apk in $^; do \
|
||||||
|
echo $(ADB) install -r $$apk; \
|
||||||
|
$(ADB) install -r $$apk; \
|
||||||
|
done
|
||||||
|
$(ADB) shell am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -n org.inodes.gus.scummvm/.Unpacker
|
||||||
|
|
||||||
|
.PHONY: androidrelease androidtest
|
414
backends/platform/android/asset-archive.cpp
Normal file
414
backends/platform/android/asset-archive.cpp
Normal file
|
@ -0,0 +1,414 @@
|
||||||
|
/* ScummVM - Graphic Adventure Engine
|
||||||
|
*
|
||||||
|
* ScummVM is the legal property of its developers, whose names
|
||||||
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||||
|
* file distributed with this source distribution.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*
|
||||||
|
* $URL$
|
||||||
|
* $Id$
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if defined(ANDROID)
|
||||||
|
|
||||||
|
#include <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 "backends/platform/android/asset-archive.h"
|
||||||
|
|
||||||
|
extern JNIEnv* JNU_GetEnv();
|
||||||
|
|
||||||
|
// 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 = static_cast<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_mark);
|
||||||
|
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 = JNU_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 = JNU_GetEnv();
|
||||||
|
|
||||||
|
if (_buflen < 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 = JNU_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));
|
||||||
|
return false; // No point looping forever...
|
||||||
|
}
|
||||||
|
_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 = JNU_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 = JNU_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 = JNU_GetEnv();
|
||||||
|
env->DeleteGlobalRef(_am);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AndroidAssetArchive::hasFile(const Common::String &name) {
|
||||||
|
JNIEnv* env = JNU_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) {
|
||||||
|
JNIEnv* env = JNU_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 = static_cast<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();
|
||||||
|
continue; // May as well keep going ...
|
||||||
|
}
|
||||||
|
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, NULL);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::ArchiveMemberPtr AndroidAssetArchive::getMember(const Common::String &name) {
|
||||||
|
return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(name, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::SeekableReadStream *AndroidAssetArchive::createReadStreamForMember(const Common::String &path) const {
|
||||||
|
JNIEnv* env = JNU_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 != NULL) {
|
||||||
|
// 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 NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JavaInputStream(env, is);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
53
backends/platform/android/asset-archive.h
Normal file
53
backends/platform/android/asset-archive.h
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
/* ScummVM - Graphic Adventure Engine
|
||||||
|
*
|
||||||
|
* ScummVM is the legal property of its developers, whose names
|
||||||
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||||
|
* file distributed with this source distribution.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*
|
||||||
|
* $URL$
|
||||||
|
* $Id$
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if defined(ANDROID)
|
||||||
|
|
||||||
|
#include <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);
|
||||||
|
virtual int listMembers(Common::ArchiveMemberList &list);
|
||||||
|
virtual Common::ArchiveMemberPtr getMember(const Common::String &name);
|
||||||
|
virtual Common::SeekableReadStream *createReadStreamForMember(const Common::String &name) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
jmethodID MID_open;
|
||||||
|
jmethodID MID_openFd;
|
||||||
|
jmethodID MID_list;
|
||||||
|
|
||||||
|
jobject _am;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
85
backends/platform/android/module.mk
Normal file
85
backends/platform/android/module.mk
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
MODULE := backends/platform/android
|
||||||
|
|
||||||
|
MODULE_OBJS := \
|
||||||
|
android.o asset-archive.o video.o
|
||||||
|
|
||||||
|
MODULE_DIRS += \
|
||||||
|
backends/platform/android/
|
||||||
|
|
||||||
|
# We don't use the rules.mk here on purpose
|
||||||
|
OBJS := $(addprefix $(MODULE)/, $(MODULE_OBJS)) $(OBJS)
|
||||||
|
|
||||||
|
JAVA_SRC = \
|
||||||
|
$(MODULE)/org/inodes/gus/scummvm/ScummVM.java \
|
||||||
|
$(MODULE)/org/inodes/gus/scummvm/ScummVMApplication.java \
|
||||||
|
$(MODULE)/org/inodes/gus/scummvm/ScummVMActivity.java \
|
||||||
|
$(MODULE)/org/inodes/gus/scummvm/EditableSurfaceView.java \
|
||||||
|
$(MODULE)/org/inodes/gus/scummvm/Unpacker.java \
|
||||||
|
$(MODULE)/org/inodes/gus/scummvm/Manifest.java \
|
||||||
|
$(MODULE)/org/inodes/gus/scummvm/R.java
|
||||||
|
|
||||||
|
JAVA_PLUGIN_SRC = \
|
||||||
|
$(MODULE)/org/inodes/gus/scummvm/PluginProvider.java
|
||||||
|
|
||||||
|
RESOURCES = \
|
||||||
|
$(srcdir)/dists/android/res/values/strings.xml \
|
||||||
|
$(srcdir)/dists/android/res/layout/main.xml \
|
||||||
|
$(srcdir)/dists/android/res/layout/splash.xml \
|
||||||
|
$(srcdir)/dists/android/res/drawable/gradient.xml \
|
||||||
|
$(srcdir)/dists/android/res/drawable/scummvm.png \
|
||||||
|
$(srcdir)/dists/android/res/drawable/scummvm_big.png
|
||||||
|
|
||||||
|
ASSETS = $(DIST_FILES_ENGINEDATA) $(DIST_FILES_THEMES)
|
||||||
|
|
||||||
|
PLUGIN_RESOURCES = \
|
||||||
|
$(srcdir)/dists/android/res/values/strings.xml \
|
||||||
|
$(srcdir)/dists/android/res/drawable/scummvm.png
|
||||||
|
|
||||||
|
# These must be incremented for each market upload
|
||||||
|
#ANDROID_VERSIONCODE = 6 Specified in dists/android/AndroidManifest.xml.in
|
||||||
|
ANDROID_PLUGIN_VERSIONCODE = 6
|
||||||
|
|
||||||
|
# This library contains scummvm proper
|
||||||
|
build.tmp/libscummvm.so: $(OBJS)
|
||||||
|
@$(MKDIR) -p $(@D)
|
||||||
|
$(CXX) $(PLUGIN_LDFLAGS) -shared $(LDFLAGS) -Wl,-soname,$(@F) -Wl,--no-undefined -o $@ $(PRE_OBJS_FLAGS) $(OBJS) $(POST_OBJS_FLAGS) $(LIBS)
|
||||||
|
|
||||||
|
|
||||||
|
backends/platform/android/org/inodes/gus/scummvm/R.java backends/platform/android/org/inodes/gus/scummvm/Manifest.java: $(srcdir)/dists/android/AndroidManifest.xml $(filter %.xml,$(RESOURCES)) $(ANDROID_JAR)
|
||||||
|
$(AAPT) package -m -J backends/platform/android -M $< -S $(srcdir)/dists/android/res -I $(ANDROID_JAR)
|
||||||
|
|
||||||
|
build.tmp/classes/%.class: $(srcdir)/backends/platform/android/%.java $(srcdir)/backends/platform/android/org/inodes/gus/scummvm/R.java
|
||||||
|
@$(MKDIR) -p $(@D)
|
||||||
|
$(JAVAC) $(JAVACFLAGS) -cp $(srcdir)/backends/platform/android -d build.tmp/classes -bootclasspath $(ANDROID_JAR) $<
|
||||||
|
|
||||||
|
build.tmp/classes.plugin/%.class: $(srcdir)/backends/platform/android/%.java
|
||||||
|
@$(MKDIR) -p $(@D)
|
||||||
|
$(JAVAC) $(JAVACFLAGS) -cp $(srcdir)/backends/platform/android -d build.tmp/classes.plugin -bootclasspath $(ANDROID_JAR) $<
|
||||||
|
|
||||||
|
classes.dex: $(JAVA_SRC:backends/platform/android/%.java=build.tmp/classes/%.class)
|
||||||
|
$(DX) --dex --output=$@ build.tmp/classes
|
||||||
|
|
||||||
|
build.tmp/plugins/classes.dex: $(JAVA_PLUGIN_SRC:backends/platform/android/%.java=build.tmp/classes.plugin/%.class)
|
||||||
|
@$(MKDIR) -p $(@D)
|
||||||
|
$(DX) --dex --output=$@ build.tmp/classes.plugin
|
||||||
|
|
||||||
|
resources.ap_: $(srcdir)/dists/android/AndroidManifest.xml $(RESOURCES) $(ASSETS) $(ANDROID_JAR) $(DIST_FILES_THEMES) $(DIST_FILES_ENGINEDATA)
|
||||||
|
$(INSTALL) -d build.tmp/assets/
|
||||||
|
$(INSTALL) -c -m 644 $(DIST_FILES_THEMES) $(DIST_FILES_ENGINEDATA) build.tmp/assets/
|
||||||
|
$(AAPT) package -f -M $< -S $(srcdir)/dists/android/res -A build.tmp/assets -I $(ANDROID_JAR) -F $@
|
||||||
|
|
||||||
|
build.tmp/%/resources.ap_: build.tmp/%/AndroidManifest.xml build.stage/%/res/values/strings.xml build.stage/%/res/drawable/scummvm.png $(ANDROID_JAR)
|
||||||
|
$(AAPT) package -f -M $< -S build.stage/$*/res -I $(ANDROID_JAR) -F $@
|
||||||
|
|
||||||
|
build.tmp/%/AndroidManifest.xml build.stage/%/res/values/strings.xml: dists/android/mkmanifest.pl configure dists/android/AndroidManifest.xml
|
||||||
|
dists/android/mkmanifest.pl --id=$* --configure=configure \
|
||||||
|
--version-name=$(VERSION) \
|
||||||
|
--version-code=$(ANDROID_PLUGIN_VERSIONCODE) \
|
||||||
|
--stringres=build.stage/$*/res/values/strings.xml \
|
||||||
|
--manifest=build.tmp/$*/AndroidManifest.xml \
|
||||||
|
--master-manifest=dists/android/AndroidManifest.xml \
|
||||||
|
--unpacklib=mylib/armeabi/lib$*.so
|
||||||
|
|
||||||
|
build.stage/%/res/drawable/scummvm.png: dists/android/res/drawable/scummvm.png
|
||||||
|
@$(MKDIR) -p $(@D)
|
||||||
|
$(CP) $< $@
|
|
@ -0,0 +1,59 @@
|
||||||
|
package org.inodes.gus.scummvm;
|
||||||
|
|
||||||
|
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 true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
return super.performEditorAction(actionCode); // Sends enter key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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();
|
||||||
|
}
|
||||||
|
}
|
330
backends/platform/android/org/inodes/gus/scummvm/Event.java
Normal file
330
backends/platform/android/org/inodes/gus/scummvm/Event.java
Normal file
|
@ -0,0 +1,330 @@
|
||||||
|
package org.inodes.gus.scummvm;
|
||||||
|
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class Event {
|
||||||
|
// Common::EventType enum.
|
||||||
|
// Must be kept in sync with common/events.h
|
||||||
|
public final static int EVENT_INVALID = 0;
|
||||||
|
public final static int EVENT_KEYDOWN = 1;
|
||||||
|
public final static int EVENT_KEYUP = 2;
|
||||||
|
public final static int EVENT_MOUSEMOVE = 3;
|
||||||
|
public final static int EVENT_LBUTTONDOWN = 4;
|
||||||
|
public final static int EVENT_LBUTTONUP = 5;
|
||||||
|
public final static int EVENT_RBUTTONDOWN = 6;
|
||||||
|
public final static int EVENT_RBUTTONUP = 7;
|
||||||
|
public final static int EVENT_WHEELUP = 8;
|
||||||
|
public final static int EVENT_WHEELDOWN = 9;
|
||||||
|
public final static int EVENT_QUIT = 10;
|
||||||
|
public final static int EVENT_SCREEN_CHANGED = 11;
|
||||||
|
public final static int EVENT_PREDICTIVE_DIALOG = 12;
|
||||||
|
public final static int EVENT_MBUTTONDOWN = 13;
|
||||||
|
public final static int EVENT_MBUTTONUP = 14;
|
||||||
|
public final static int EVENT_MAINMENU = 15;
|
||||||
|
public final static int EVENT_RTL = 16;
|
||||||
|
|
||||||
|
// common/keyboard.h
|
||||||
|
public final static int ASCII_F1 = 315;
|
||||||
|
public final static int ASCII_F2 = 316;
|
||||||
|
public final static int ASCII_F3 = 317;
|
||||||
|
public final static int ASCII_F4 = 318;
|
||||||
|
public final static int ASCII_F5 = 319;
|
||||||
|
public final static int ASCII_F6 = 320;
|
||||||
|
public final static int ASCII_F7 = 321;
|
||||||
|
public final static int ASCII_F8 = 322;
|
||||||
|
public final static int ASCII_F9 = 323;
|
||||||
|
public final static int ASCII_F10 = 324;
|
||||||
|
public final static int ASCII_F11 = 325;
|
||||||
|
public final static int ASCII_F12 = 326;
|
||||||
|
public final static int KBD_CTRL = 1 << 0;
|
||||||
|
public final static int KBD_ALT = 1 << 1;
|
||||||
|
public final static int KBD_SHIFT = 1 << 2;
|
||||||
|
|
||||||
|
public final static int KEYCODE_INVALID = 0;
|
||||||
|
public final static int KEYCODE_BACKSPACE = 8;
|
||||||
|
public final static int KEYCODE_TAB = 9;
|
||||||
|
public final static int KEYCODE_CLEAR = 12;
|
||||||
|
public final static int KEYCODE_RETURN = 13;
|
||||||
|
public final static int KEYCODE_PAUSE = 19;
|
||||||
|
public final static int KEYCODE_ESCAPE = 27;
|
||||||
|
public final static int KEYCODE_SPACE = 32;
|
||||||
|
public final static int KEYCODE_EXCLAIM = 33;
|
||||||
|
public final static int KEYCODE_QUOTEDBL = 34;
|
||||||
|
public final static int KEYCODE_HASH = 35;
|
||||||
|
public final static int KEYCODE_DOLLAR = 36;
|
||||||
|
public final static int KEYCODE_AMPERSAND = 38;
|
||||||
|
public final static int KEYCODE_QUOTE = 39;
|
||||||
|
public final static int KEYCODE_LEFTPAREN = 40;
|
||||||
|
public final static int KEYCODE_RIGHTPAREN = 41;
|
||||||
|
public final static int KEYCODE_ASTERISK = 42;
|
||||||
|
public final static int KEYCODE_PLUS = 43;
|
||||||
|
public final static int KEYCODE_COMMA = 44;
|
||||||
|
public final static int KEYCODE_MINUS = 45;
|
||||||
|
public final static int KEYCODE_PERIOD = 46;
|
||||||
|
public final static int KEYCODE_SLASH = 47;
|
||||||
|
public final static int KEYCODE_0 = 48;
|
||||||
|
public final static int KEYCODE_1 = 49;
|
||||||
|
public final static int KEYCODE_2 = 50;
|
||||||
|
public final static int KEYCODE_3 = 51;
|
||||||
|
public final static int KEYCODE_4 = 52;
|
||||||
|
public final static int KEYCODE_5 = 53;
|
||||||
|
public final static int KEYCODE_6 = 54;
|
||||||
|
public final static int KEYCODE_7 = 55;
|
||||||
|
public final static int KEYCODE_8 = 56;
|
||||||
|
public final static int KEYCODE_9 = 57;
|
||||||
|
public final static int KEYCODE_COLON = 58;
|
||||||
|
public final static int KEYCODE_SEMICOLON = 59;
|
||||||
|
public final static int KEYCODE_LESS = 60;
|
||||||
|
public final static int KEYCODE_EQUALS = 61;
|
||||||
|
public final static int KEYCODE_GREATER = 62;
|
||||||
|
public final static int KEYCODE_QUESTION = 63;
|
||||||
|
public final static int KEYCODE_AT = 64;
|
||||||
|
public final static int KEYCODE_LEFTBRACKET = 91;
|
||||||
|
public final static int KEYCODE_BACKSLASH = 92;
|
||||||
|
public final static int KEYCODE_RIGHTBRACKET = 93;
|
||||||
|
public final static int KEYCODE_CARET = 94;
|
||||||
|
public final static int KEYCODE_UNDERSCORE = 95;
|
||||||
|
public final static int KEYCODE_BACKQUOTE = 96;
|
||||||
|
public final static int KEYCODE_a = 97;
|
||||||
|
public final static int KEYCODE_b = 98;
|
||||||
|
public final static int KEYCODE_c = 99;
|
||||||
|
public final static int KEYCODE_d = 100;
|
||||||
|
public final static int KEYCODE_e = 101;
|
||||||
|
public final static int KEYCODE_f = 102;
|
||||||
|
public final static int KEYCODE_g = 103;
|
||||||
|
public final static int KEYCODE_h = 104;
|
||||||
|
public final static int KEYCODE_i = 105;
|
||||||
|
public final static int KEYCODE_j = 106;
|
||||||
|
public final static int KEYCODE_k = 107;
|
||||||
|
public final static int KEYCODE_l = 108;
|
||||||
|
public final static int KEYCODE_m = 109;
|
||||||
|
public final static int KEYCODE_n = 110;
|
||||||
|
public final static int KEYCODE_o = 111;
|
||||||
|
public final static int KEYCODE_p = 112;
|
||||||
|
public final static int KEYCODE_q = 113;
|
||||||
|
public final static int KEYCODE_r = 114;
|
||||||
|
public final static int KEYCODE_s = 115;
|
||||||
|
public final static int KEYCODE_t = 116;
|
||||||
|
public final static int KEYCODE_u = 117;
|
||||||
|
public final static int KEYCODE_v = 118;
|
||||||
|
public final static int KEYCODE_w = 119;
|
||||||
|
public final static int KEYCODE_x = 120;
|
||||||
|
public final static int KEYCODE_y = 121;
|
||||||
|
public final static int KEYCODE_z = 122;
|
||||||
|
public final static int KEYCODE_DELETE = 127;
|
||||||
|
// Numeric keypad
|
||||||
|
public final static int KEYCODE_KP0 = 256;
|
||||||
|
public final static int KEYCODE_KP1 = 257;
|
||||||
|
public final static int KEYCODE_KP2 = 258;
|
||||||
|
public final static int KEYCODE_KP3 = 259;
|
||||||
|
public final static int KEYCODE_KP4 = 260;
|
||||||
|
public final static int KEYCODE_KP5 = 261;
|
||||||
|
public final static int KEYCODE_KP6 = 262;
|
||||||
|
public final static int KEYCODE_KP7 = 263;
|
||||||
|
public final static int KEYCODE_KP8 = 264;
|
||||||
|
public final static int KEYCODE_KP9 = 265;
|
||||||
|
public final static int KEYCODE_KP_PERIOD = 266;
|
||||||
|
public final static int KEYCODE_KP_DIVIDE = 267;
|
||||||
|
public final static int KEYCODE_KP_MULTIPLY = 268;
|
||||||
|
public final static int KEYCODE_KP_MINUS = 269;
|
||||||
|
public final static int KEYCODE_KP_PLUS = 270;
|
||||||
|
public final static int KEYCODE_KP_ENTER = 271;
|
||||||
|
public final static int KEYCODE_KP_EQUALS = 272;
|
||||||
|
// Arrows + Home/End pad
|
||||||
|
public final static int KEYCODE_UP = 273;
|
||||||
|
public final static int KEYCODE_DOWN = 274;
|
||||||
|
public final static int KEYCODE_RIGHT = 275;
|
||||||
|
public final static int KEYCODE_LEFT = 276;
|
||||||
|
public final static int KEYCODE_INSERT = 277;
|
||||||
|
public final static int KEYCODE_HOME = 278;
|
||||||
|
public final static int KEYCODE_END = 279;
|
||||||
|
public final static int KEYCODE_PAGEUP = 280;
|
||||||
|
public final static int KEYCODE_PAGEDOWN = 281;
|
||||||
|
// Function keys
|
||||||
|
public final static int KEYCODE_F1 = 282;
|
||||||
|
public final static int KEYCODE_F2 = 283;
|
||||||
|
public final static int KEYCODE_F3 = 284;
|
||||||
|
public final static int KEYCODE_F4 = 285;
|
||||||
|
public final static int KEYCODE_F5 = 286;
|
||||||
|
public final static int KEYCODE_F6 = 287;
|
||||||
|
public final static int KEYCODE_F7 = 288;
|
||||||
|
public final static int KEYCODE_F8 = 289;
|
||||||
|
public final static int KEYCODE_F9 = 290;
|
||||||
|
public final static int KEYCODE_F10 = 291;
|
||||||
|
public final static int KEYCODE_F11 = 292;
|
||||||
|
public final static int KEYCODE_F12 = 293;
|
||||||
|
public final static int KEYCODE_F13 = 294;
|
||||||
|
public final static int KEYCODE_F14 = 295;
|
||||||
|
public final static int KEYCODE_F15 = 296;
|
||||||
|
// Key state modifier keys
|
||||||
|
public final static int KEYCODE_NUMLOCK = 300;
|
||||||
|
public final static int KEYCODE_CAPSLOCK = 301;
|
||||||
|
public final static int KEYCODE_SCROLLOCK = 302;
|
||||||
|
public final static int KEYCODE_RSHIFT = 303;
|
||||||
|
public final static int KEYCODE_LSHIFT = 304;
|
||||||
|
public final static int KEYCODE_RCTRL = 305;
|
||||||
|
public final static int KEYCODE_LCTRL = 306;
|
||||||
|
public final static int KEYCODE_RALT = 307;
|
||||||
|
public final static int KEYCODE_LALT = 308;
|
||||||
|
public final static int KEYCODE_RMETA = 309;
|
||||||
|
public final static int KEYCODE_LMETA = 310;
|
||||||
|
public final static int KEYCODE_LSUPER = 311; // Left "Windows" key
|
||||||
|
public final static int KEYCODE_RSUPER = 312; // Right "Windows" key
|
||||||
|
public final static int KEYCODE_MODE = 313; // "Alt Gr" key
|
||||||
|
public final static int KEYCODE_COMPOSE = 314; // Multi-key compose key
|
||||||
|
// Miscellaneous function keys
|
||||||
|
public final static int KEYCODE_HELP = 315;
|
||||||
|
public final static int KEYCODE_PRINT = 316;
|
||||||
|
public final static int KEYCODE_SYSREQ = 317;
|
||||||
|
public final static int KEYCODE_BREAK = 318;
|
||||||
|
public final static int KEYCODE_MENU = 319;
|
||||||
|
public final static int KEYCODE_POWER = 320; // Power Macintosh power key
|
||||||
|
public final static int KEYCODE_EURO = 321; // Some european keyboards
|
||||||
|
public final static int KEYCODE_UNDO = 322; // Atari keyboard has Undo
|
||||||
|
|
||||||
|
// Android KeyEvent keycode -> ScummVM keycode
|
||||||
|
public final static Map<Integer, Integer> androidKeyMap;
|
||||||
|
static {
|
||||||
|
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
|
||||||
|
|
||||||
|
map.put(KeyEvent.KEYCODE_DEL, KEYCODE_BACKSPACE);
|
||||||
|
map.put(KeyEvent.KEYCODE_TAB, KEYCODE_TAB);
|
||||||
|
map.put(KeyEvent.KEYCODE_CLEAR, KEYCODE_CLEAR);
|
||||||
|
map.put(KeyEvent.KEYCODE_ENTER, KEYCODE_RETURN);
|
||||||
|
//map.put(??, KEYCODE_PAUSE);
|
||||||
|
map.put(KeyEvent.KEYCODE_BACK, KEYCODE_ESCAPE);
|
||||||
|
map.put(KeyEvent.KEYCODE_SPACE, KEYCODE_SPACE);
|
||||||
|
//map.put(??, KEYCODE_EXCLAIM);
|
||||||
|
//map.put(??, KEYCODE_QUOTEDBL);
|
||||||
|
map.put(KeyEvent.KEYCODE_POUND, KEYCODE_HASH);
|
||||||
|
//map.put(??, KEYCODE_DOLLAR);
|
||||||
|
//map.put(??, KEYCODE_AMPERSAND);
|
||||||
|
map.put(KeyEvent.KEYCODE_APOSTROPHE, KEYCODE_QUOTE);
|
||||||
|
//map.put(??, KEYCODE_LEFTPAREN);
|
||||||
|
//map.put(??, KEYCODE_RIGHTPAREN);
|
||||||
|
//map.put(??, KEYCODE_ASTERISK);
|
||||||
|
map.put(KeyEvent.KEYCODE_PLUS, KEYCODE_PLUS);
|
||||||
|
map.put(KeyEvent.KEYCODE_COMMA, KEYCODE_COMMA);
|
||||||
|
map.put(KeyEvent.KEYCODE_MINUS, KEYCODE_MINUS);
|
||||||
|
map.put(KeyEvent.KEYCODE_PERIOD, KEYCODE_PERIOD);
|
||||||
|
map.put(KeyEvent.KEYCODE_SLASH, KEYCODE_SLASH);
|
||||||
|
map.put(KeyEvent.KEYCODE_0, KEYCODE_0);
|
||||||
|
map.put(KeyEvent.KEYCODE_1, KEYCODE_1);
|
||||||
|
map.put(KeyEvent.KEYCODE_2, KEYCODE_2);
|
||||||
|
map.put(KeyEvent.KEYCODE_3, KEYCODE_3);
|
||||||
|
map.put(KeyEvent.KEYCODE_4, KEYCODE_4);
|
||||||
|
map.put(KeyEvent.KEYCODE_5, KEYCODE_5);
|
||||||
|
map.put(KeyEvent.KEYCODE_6, KEYCODE_6);
|
||||||
|
map.put(KeyEvent.KEYCODE_7, KEYCODE_7);
|
||||||
|
map.put(KeyEvent.KEYCODE_8, KEYCODE_8);
|
||||||
|
map.put(KeyEvent.KEYCODE_9, KEYCODE_9);
|
||||||
|
//map.put(??, KEYCODE_COLON);
|
||||||
|
map.put(KeyEvent.KEYCODE_SEMICOLON, KEYCODE_SEMICOLON);
|
||||||
|
//map.put(??, KEYCODE_LESS);
|
||||||
|
map.put(KeyEvent.KEYCODE_EQUALS, KEYCODE_EQUALS);
|
||||||
|
//map.put(??, KEYCODE_GREATER);
|
||||||
|
//map.put(??, KEYCODE_QUESTION);
|
||||||
|
map.put(KeyEvent.KEYCODE_AT, KEYCODE_AT);
|
||||||
|
map.put(KeyEvent.KEYCODE_LEFT_BRACKET, KEYCODE_LEFTBRACKET);
|
||||||
|
map.put(KeyEvent.KEYCODE_BACKSLASH, KEYCODE_BACKSLASH);
|
||||||
|
map.put(KeyEvent.KEYCODE_RIGHT_BRACKET, KEYCODE_RIGHTBRACKET);
|
||||||
|
//map.put(??, KEYCODE_CARET);
|
||||||
|
//map.put(??, KEYCODE_UNDERSCORE);
|
||||||
|
//map.put(??, KEYCODE_BACKQUOTE);
|
||||||
|
map.put(KeyEvent.KEYCODE_A, KEYCODE_a);
|
||||||
|
map.put(KeyEvent.KEYCODE_B, KEYCODE_b);
|
||||||
|
map.put(KeyEvent.KEYCODE_C, KEYCODE_c);
|
||||||
|
map.put(KeyEvent.KEYCODE_D, KEYCODE_d);
|
||||||
|
map.put(KeyEvent.KEYCODE_E, KEYCODE_e);
|
||||||
|
map.put(KeyEvent.KEYCODE_F, KEYCODE_f);
|
||||||
|
map.put(KeyEvent.KEYCODE_G, KEYCODE_g);
|
||||||
|
map.put(KeyEvent.KEYCODE_H, KEYCODE_h);
|
||||||
|
map.put(KeyEvent.KEYCODE_I, KEYCODE_i);
|
||||||
|
map.put(KeyEvent.KEYCODE_J, KEYCODE_j);
|
||||||
|
map.put(KeyEvent.KEYCODE_K, KEYCODE_k);
|
||||||
|
map.put(KeyEvent.KEYCODE_L, KEYCODE_l);
|
||||||
|
map.put(KeyEvent.KEYCODE_M, KEYCODE_m);
|
||||||
|
map.put(KeyEvent.KEYCODE_N, KEYCODE_n);
|
||||||
|
map.put(KeyEvent.KEYCODE_O, KEYCODE_o);
|
||||||
|
map.put(KeyEvent.KEYCODE_P, KEYCODE_p);
|
||||||
|
map.put(KeyEvent.KEYCODE_Q, KEYCODE_q);
|
||||||
|
map.put(KeyEvent.KEYCODE_R, KEYCODE_r);
|
||||||
|
map.put(KeyEvent.KEYCODE_S, KEYCODE_s);
|
||||||
|
map.put(KeyEvent.KEYCODE_T, KEYCODE_t);
|
||||||
|
map.put(KeyEvent.KEYCODE_U, KEYCODE_u);
|
||||||
|
map.put(KeyEvent.KEYCODE_V, KEYCODE_v);
|
||||||
|
map.put(KeyEvent.KEYCODE_W, KEYCODE_w);
|
||||||
|
map.put(KeyEvent.KEYCODE_X, KEYCODE_x);
|
||||||
|
map.put(KeyEvent.KEYCODE_Y, KEYCODE_y);
|
||||||
|
map.put(KeyEvent.KEYCODE_Z, KEYCODE_z);
|
||||||
|
//map.put(KeyEvent.KEYCODE_DEL, KEYCODE_DELETE); use BACKSPACE instead
|
||||||
|
//map.put(??, KEYCODE_KP_*);
|
||||||
|
map.put(KeyEvent.KEYCODE_DPAD_UP, KEYCODE_UP);
|
||||||
|
map.put(KeyEvent.KEYCODE_DPAD_DOWN, KEYCODE_DOWN);
|
||||||
|
map.put(KeyEvent.KEYCODE_DPAD_RIGHT, KEYCODE_RIGHT);
|
||||||
|
map.put(KeyEvent.KEYCODE_DPAD_LEFT, KEYCODE_LEFT);
|
||||||
|
//map.put(??, KEYCODE_INSERT);
|
||||||
|
//map.put(??, KEYCODE_HOME);
|
||||||
|
//map.put(??, KEYCODE_END);
|
||||||
|
//map.put(??, KEYCODE_PAGEUP);
|
||||||
|
//map.put(??, KEYCODE_PAGEDOWN);
|
||||||
|
//map.put(??, KEYCODE_F{1-15});
|
||||||
|
map.put(KeyEvent.KEYCODE_NUM, KEYCODE_NUMLOCK);
|
||||||
|
//map.put(??, KEYCODE_CAPSLOCK);
|
||||||
|
//map.put(??, KEYCODE_SCROLLLOCK);
|
||||||
|
map.put(KeyEvent.KEYCODE_SHIFT_RIGHT, KEYCODE_RSHIFT);
|
||||||
|
map.put(KeyEvent.KEYCODE_SHIFT_LEFT, KEYCODE_LSHIFT);
|
||||||
|
//map.put(??, KEYCODE_RCTRL);
|
||||||
|
//map.put(??, KEYCODE_LCTRL);
|
||||||
|
map.put(KeyEvent.KEYCODE_ALT_RIGHT, KEYCODE_RALT);
|
||||||
|
map.put(KeyEvent.KEYCODE_ALT_LEFT, KEYCODE_LALT);
|
||||||
|
// ?? META, SUPER
|
||||||
|
// ?? MODE, COMPOSE
|
||||||
|
// ?? HELP, PRINT, SYSREQ, BREAK, EURO, UNDO
|
||||||
|
map.put(KeyEvent.KEYCODE_MENU, KEYCODE_MENU);
|
||||||
|
map.put(KeyEvent.KEYCODE_POWER, KEYCODE_POWER);
|
||||||
|
|
||||||
|
androidKeyMap = Collections.unmodifiableMap(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int type;
|
||||||
|
public boolean synthetic;
|
||||||
|
public int kbd_keycode;
|
||||||
|
public int kbd_ascii;
|
||||||
|
public int kbd_flags;
|
||||||
|
public int mouse_x;
|
||||||
|
public int mouse_y;
|
||||||
|
public boolean mouse_relative; // Used for trackball events
|
||||||
|
|
||||||
|
public Event() {
|
||||||
|
type = EVENT_INVALID;
|
||||||
|
synthetic = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Event(int type) {
|
||||||
|
this.type = type;
|
||||||
|
synthetic = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Event KeyboardEvent(int type, int keycode, int ascii,
|
||||||
|
int flags) {
|
||||||
|
Event e = new Event();
|
||||||
|
e.type = type;
|
||||||
|
e.kbd_keycode = keycode;
|
||||||
|
e.kbd_ascii = ascii;
|
||||||
|
e.kbd_flags = flags;
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Event MouseEvent(int type, int x, int y) {
|
||||||
|
Event e = new Event();
|
||||||
|
e.type = type;
|
||||||
|
e.mouse_x = x;
|
||||||
|
e.mouse_y = y;
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package org.inodes.gus.scummvm;
|
||||||
|
|
||||||
|
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.PackageManager;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class PluginProvider extends BroadcastReceiver {
|
||||||
|
public final static String META_UNPACK_LIB =
|
||||||
|
"org.inodes.gus.scummvm.meta.UNPACK_LIB";
|
||||||
|
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
if (!intent.getAction().equals(ScummVMApplication.ACTION_PLUGIN_QUERY))
|
||||||
|
return;
|
||||||
|
|
||||||
|
Bundle extras = getResultExtras(true);
|
||||||
|
|
||||||
|
final ActivityInfo info;
|
||||||
|
try {
|
||||||
|
info = context.getPackageManager()
|
||||||
|
.getReceiverInfo(new ComponentName(context, this.getClass()),
|
||||||
|
PackageManager.GET_META_DATA);
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
Log.e(this.toString(), "Error finding my own info?", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String mylib = info.metaData.getString(META_UNPACK_LIB);
|
||||||
|
if (mylib != null) {
|
||||||
|
ArrayList<String> all_libs =
|
||||||
|
extras.getStringArrayList(ScummVMApplication.EXTRA_UNPACK_LIBS);
|
||||||
|
|
||||||
|
all_libs.add(new Uri.Builder()
|
||||||
|
.scheme("plugin")
|
||||||
|
.authority(context.getPackageName())
|
||||||
|
.path(mylib)
|
||||||
|
.toString());
|
||||||
|
|
||||||
|
extras.putStringArrayList(ScummVMApplication.EXTRA_UNPACK_LIBS,
|
||||||
|
all_libs);
|
||||||
|
}
|
||||||
|
|
||||||
|
setResultExtras(extras);
|
||||||
|
}
|
||||||
|
}
|
317
backends/platform/android/org/inodes/gus/scummvm/ScummVM.java
Normal file
317
backends/platform/android/org/inodes/gus/scummvm/ScummVM.java
Normal file
|
@ -0,0 +1,317 @@
|
||||||
|
package org.inodes.gus.scummvm;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.AssetManager;
|
||||||
|
import android.media.AudioFormat;
|
||||||
|
import android.media.AudioManager;
|
||||||
|
import android.media.AudioTrack;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.HandlerThread;
|
||||||
|
import android.os.Process;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.Surface;
|
||||||
|
import android.view.SurfaceHolder;
|
||||||
|
|
||||||
|
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.concurrent.Semaphore;
|
||||||
|
|
||||||
|
|
||||||
|
// At least in Android 2.1, eglCreateWindowSurface() requires an
|
||||||
|
// EGLNativeWindowSurface object, which is hidden deep in the bowels
|
||||||
|
// of libui. Until EGL is properly exposed, it's probably safer to
|
||||||
|
// use the Java versions of most EGL functions :(
|
||||||
|
|
||||||
|
public class ScummVM implements SurfaceHolder.Callback {
|
||||||
|
private final static String LOG_TAG = "ScummVM.java";
|
||||||
|
|
||||||
|
private final int AUDIO_FRAME_SIZE = 2 * 2; // bytes. 16bit audio * stereo
|
||||||
|
public static class AudioSetupException extends Exception {}
|
||||||
|
|
||||||
|
private long nativeScummVM; // native code hangs itself here
|
||||||
|
boolean scummVMRunning = false;
|
||||||
|
|
||||||
|
private native void create(AssetManager am);
|
||||||
|
|
||||||
|
public ScummVM(Context context) {
|
||||||
|
create(context.getAssets()); // Init C++ code, set nativeScummVM
|
||||||
|
}
|
||||||
|
|
||||||
|
private native void nativeDestroy();
|
||||||
|
|
||||||
|
public synchronized void destroy() {
|
||||||
|
if (nativeScummVM != 0) {
|
||||||
|
nativeDestroy();
|
||||||
|
nativeScummVM = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected void finalize() {
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Surface creation:
|
||||||
|
// GUI thread: create surface, release lock
|
||||||
|
// ScummVM thread: acquire lock (block), read surface
|
||||||
|
//
|
||||||
|
// Surface deletion:
|
||||||
|
// GUI thread: post event, acquire lock (block), return
|
||||||
|
// ScummVM thread: read event, free surface, release lock
|
||||||
|
//
|
||||||
|
// In other words, ScummVM thread does this:
|
||||||
|
// acquire lock
|
||||||
|
// setup surface
|
||||||
|
// when SCREEN_CHANGED arrives:
|
||||||
|
// destroy surface
|
||||||
|
// release lock
|
||||||
|
// back to acquire lock
|
||||||
|
static final int configSpec[] = {
|
||||||
|
EGL10.EGL_RED_SIZE, 5,
|
||||||
|
EGL10.EGL_GREEN_SIZE, 5,
|
||||||
|
EGL10.EGL_BLUE_SIZE, 5,
|
||||||
|
EGL10.EGL_DEPTH_SIZE, 0,
|
||||||
|
EGL10.EGL_SURFACE_TYPE, EGL10.EGL_WINDOW_BIT,
|
||||||
|
EGL10.EGL_NONE,
|
||||||
|
};
|
||||||
|
EGL10 egl;
|
||||||
|
EGLDisplay eglDisplay = EGL10.EGL_NO_DISPLAY;
|
||||||
|
EGLConfig eglConfig;
|
||||||
|
EGLContext eglContext = EGL10.EGL_NO_CONTEXT;
|
||||||
|
EGLSurface eglSurface = EGL10.EGL_NO_SURFACE;
|
||||||
|
Semaphore surfaceLock = new Semaphore(0, true);
|
||||||
|
SurfaceHolder nativeSurface;
|
||||||
|
|
||||||
|
public void surfaceCreated(SurfaceHolder holder) {
|
||||||
|
nativeSurface = holder;
|
||||||
|
surfaceLock.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void surfaceChanged(SurfaceHolder holder, int format,
|
||||||
|
int width, int height) {
|
||||||
|
// Disabled while I debug GL problems
|
||||||
|
//pushEvent(new Event(Event.EVENT_SCREEN_CHANGED));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||||
|
pushEvent(new Event(Event.EVENT_SCREEN_CHANGED));
|
||||||
|
try {
|
||||||
|
surfaceLock.acquire();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Log.e(this.toString(),
|
||||||
|
"Interrupted while waiting for surface lock", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called by ScummVM thread (from initBackend)
|
||||||
|
private void createScummVMGLContext() {
|
||||||
|
egl = (EGL10)EGLContext.getEGL();
|
||||||
|
eglDisplay = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
|
||||||
|
int[] version = new int[2];
|
||||||
|
egl.eglInitialize(eglDisplay, version);
|
||||||
|
int[] num_config = new int[1];
|
||||||
|
egl.eglChooseConfig(eglDisplay, configSpec, null, 0, num_config);
|
||||||
|
|
||||||
|
final int numConfigs = num_config[0];
|
||||||
|
if (numConfigs <= 0)
|
||||||
|
throw new IllegalArgumentException("No configs match configSpec");
|
||||||
|
|
||||||
|
EGLConfig[] configs = new EGLConfig[numConfigs];
|
||||||
|
egl.eglChooseConfig(eglDisplay, configSpec, configs, numConfigs,
|
||||||
|
num_config);
|
||||||
|
eglConfig = configs[0];
|
||||||
|
|
||||||
|
eglContext = egl.eglCreateContext(eglDisplay, eglConfig,
|
||||||
|
EGL10.EGL_NO_CONTEXT, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called by ScummVM thread
|
||||||
|
protected void setupScummVMSurface() {
|
||||||
|
try {
|
||||||
|
surfaceLock.acquire();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Log.e(this.toString(),
|
||||||
|
"Interrupted while waiting for surface lock", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
eglSurface = egl.eglCreateWindowSurface(eglDisplay, eglConfig,
|
||||||
|
nativeSurface, null);
|
||||||
|
egl.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called by ScummVM thread
|
||||||
|
protected void destroyScummVMSurface() {
|
||||||
|
if (eglSurface != null) {
|
||||||
|
egl.eglMakeCurrent(eglDisplay, EGL10.EGL_NO_SURFACE,
|
||||||
|
EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
|
||||||
|
egl.eglDestroySurface(eglDisplay, eglSurface);
|
||||||
|
eglSurface = EGL10.EGL_NO_SURFACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
surfaceLock.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSurface(SurfaceHolder holder) {
|
||||||
|
holder.addCallback(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set scummvm config options
|
||||||
|
final public native static void loadConfigFile(String path);
|
||||||
|
final public native static void setConfMan(String key, int value);
|
||||||
|
final public native static void setConfMan(String key, String value);
|
||||||
|
|
||||||
|
// Feed an event to ScummVM. Safe to call from other threads.
|
||||||
|
final public native void pushEvent(Event e);
|
||||||
|
|
||||||
|
final private native void audioMixCallback(byte[] buf);
|
||||||
|
|
||||||
|
// Runs the actual ScummVM program and returns when it does.
|
||||||
|
// This should not be called from multiple threads simultaneously...
|
||||||
|
final public native int scummVMMain(String[] argv);
|
||||||
|
|
||||||
|
// Callbacks from C++ peer instance
|
||||||
|
//protected GraphicsMode[] getSupportedGraphicsModes() {}
|
||||||
|
protected void displayMessageOnOSD(String msg) {}
|
||||||
|
protected void setWindowCaption(String caption) {}
|
||||||
|
protected void showVirtualKeyboard(boolean enable) {}
|
||||||
|
protected String[] getSysArchives() { return new String[0]; }
|
||||||
|
protected String[] getPluginDirectories() { return new String[0]; }
|
||||||
|
protected void initBackend() throws AudioSetupException {
|
||||||
|
createScummVMGLContext();
|
||||||
|
initAudio();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class AudioThread extends Thread {
|
||||||
|
final private int buf_size;
|
||||||
|
private boolean is_paused = false;
|
||||||
|
final private ScummVM scummvm;
|
||||||
|
final private AudioTrack audio_track;
|
||||||
|
|
||||||
|
AudioThread(ScummVM scummvm, AudioTrack audio_track, int buf_size) {
|
||||||
|
super("AudioThread");
|
||||||
|
this.scummvm = scummvm;
|
||||||
|
this.audio_track = audio_track;
|
||||||
|
this.buf_size = buf_size;
|
||||||
|
setPriority(Thread.MAX_PRIORITY);
|
||||||
|
setDaemon(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pauseAudio() {
|
||||||
|
synchronized (this) {
|
||||||
|
is_paused = true;
|
||||||
|
}
|
||||||
|
audio_track.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resumeAudio() {
|
||||||
|
synchronized (this) {
|
||||||
|
is_paused = false;
|
||||||
|
notifyAll();
|
||||||
|
}
|
||||||
|
audio_track.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
byte[] buf = new byte[buf_size];
|
||||||
|
audio_track.play();
|
||||||
|
int offset = 0;
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
synchronized (this) {
|
||||||
|
while (is_paused)
|
||||||
|
wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset == buf.length) {
|
||||||
|
// Grab new audio data
|
||||||
|
scummvm.audioMixCallback(buf);
|
||||||
|
offset = 0;
|
||||||
|
}
|
||||||
|
int len = buf.length - offset;
|
||||||
|
int ret = audio_track.write(buf, offset, len);
|
||||||
|
if (ret < 0) {
|
||||||
|
Log.w(LOG_TAG, String.format(
|
||||||
|
"AudioTrack.write(%dB) returned error %d",
|
||||||
|
buf.length, ret));
|
||||||
|
break;
|
||||||
|
} else if (ret != len) {
|
||||||
|
Log.w(LOG_TAG, String.format(
|
||||||
|
"Short audio write. Wrote %dB, not %dB",
|
||||||
|
ret, buf.length));
|
||||||
|
// Buffer is full, so yield cpu for a while
|
||||||
|
Thread.sleep(100);
|
||||||
|
}
|
||||||
|
offset += ret;
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Log.e(this.toString(), "Audio thread interrupted", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private AudioThread audio_thread;
|
||||||
|
|
||||||
|
final public int audioSampleRate() {
|
||||||
|
return AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initAudio() throws AudioSetupException {
|
||||||
|
int sample_rate = audioSampleRate();
|
||||||
|
int buf_size =
|
||||||
|
AudioTrack.getMinBufferSize(sample_rate,
|
||||||
|
AudioFormat.CHANNEL_CONFIGURATION_STEREO,
|
||||||
|
AudioFormat.ENCODING_PCM_16BIT);
|
||||||
|
if (buf_size < 0) {
|
||||||
|
int guess = AUDIO_FRAME_SIZE * sample_rate / 100; // 10ms of audio
|
||||||
|
Log.w(LOG_TAG, String.format(
|
||||||
|
"Unable to get min audio buffer size (error %d). Guessing %dB.",
|
||||||
|
buf_size, guess));
|
||||||
|
buf_size = guess;
|
||||||
|
}
|
||||||
|
Log.d(LOG_TAG, String.format("Using %dB buffer for %dHZ audio",
|
||||||
|
buf_size, sample_rate));
|
||||||
|
AudioTrack audio_track =
|
||||||
|
new AudioTrack(AudioManager.STREAM_MUSIC,
|
||||||
|
sample_rate,
|
||||||
|
AudioFormat.CHANNEL_CONFIGURATION_STEREO,
|
||||||
|
AudioFormat.ENCODING_PCM_16BIT,
|
||||||
|
buf_size,
|
||||||
|
AudioTrack.MODE_STREAM);
|
||||||
|
if (audio_track.getState() != AudioTrack.STATE_INITIALIZED) {
|
||||||
|
Log.e(LOG_TAG, "Error initialising Android audio system.");
|
||||||
|
throw new AudioSetupException();
|
||||||
|
}
|
||||||
|
|
||||||
|
audio_thread = new AudioThread(this, audio_track, buf_size);
|
||||||
|
audio_thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pause() {
|
||||||
|
audio_thread.pauseAudio();
|
||||||
|
// TODO: need to pause engine too
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resume() {
|
||||||
|
// TODO: need to resume engine too
|
||||||
|
audio_thread.resumeAudio();
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
// For grabbing with gdb...
|
||||||
|
final boolean sleep_for_debugger = false;
|
||||||
|
if (sleep_for_debugger) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(20*1000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//System.loadLibrary("scummvm");
|
||||||
|
File cache_dir = ScummVMApplication.getLastCacheDir();
|
||||||
|
String libname = System.mapLibraryName("scummvm");
|
||||||
|
File libpath = new File(cache_dir, libname);
|
||||||
|
System.load(libpath.getPath());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,446 @@
|
||||||
|
package org.inodes.gus.scummvm;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.media.AudioManager;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.SurfaceView;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewConfiguration;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class ScummVMActivity extends Activity {
|
||||||
|
private boolean _do_right_click;
|
||||||
|
private boolean _last_click_was_right;
|
||||||
|
|
||||||
|
// game pixels to move per trackball/dpad event.
|
||||||
|
// FIXME: replace this with proper mouse acceleration
|
||||||
|
private final static int TRACKBALL_SCALE = 2;
|
||||||
|
|
||||||
|
private class MyScummVM extends ScummVM {
|
||||||
|
private boolean scummvmRunning = false;
|
||||||
|
|
||||||
|
public MyScummVM() {
|
||||||
|
super(ScummVMActivity.this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initBackend() throws ScummVM.AudioSetupException {
|
||||||
|
synchronized (this) {
|
||||||
|
scummvmRunning = true;
|
||||||
|
notifyAll();
|
||||||
|
}
|
||||||
|
super.initBackend();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void waitUntilRunning() throws InterruptedException {
|
||||||
|
synchronized (this) {
|
||||||
|
while (!scummvmRunning)
|
||||||
|
wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void displayMessageOnOSD(String msg) {
|
||||||
|
Log.i(this.toString(), "OSD: " + msg);
|
||||||
|
Toast.makeText(ScummVMActivity.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] = ScummVMApplication.getLastCacheDir().getPath();
|
||||||
|
return dirs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void showVirtualKeyboard(final boolean enable) {
|
||||||
|
if (getResources().getConfiguration().keyboard ==
|
||||||
|
Configuration.KEYBOARD_NOKEYS) {
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
showKeyboard(enable);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private MyScummVM scummvm;
|
||||||
|
private Thread scummvm_thread;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
_do_right_click = false;
|
||||||
|
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.setOnTouchListener(new View.OnTouchListener() {
|
||||||
|
public boolean onTouch(View v, MotionEvent event) {
|
||||||
|
return onTouchEvent(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
main_surface.setOnKeyListener(new View.OnKeyListener() {
|
||||||
|
public boolean onKey(View v, int code, KeyEvent ev) {
|
||||||
|
return onKeyDown(code, ev);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
main_surface.requestFocus();
|
||||||
|
|
||||||
|
// Start ScummVM
|
||||||
|
scummvm = new MyScummVM();
|
||||||
|
scummvm_thread = new Thread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
runScummVM();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e("ScummVM", "Fatal error in ScummVM thread", e);
|
||||||
|
new AlertDialog.Builder(ScummVMActivity.this)
|
||||||
|
.setTitle("Error")
|
||||||
|
.setMessage(e.toString())
|
||||||
|
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||||
|
.show();
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, "ScummVM");
|
||||||
|
scummvm_thread.start();
|
||||||
|
|
||||||
|
// Block UI thread until ScummVM has started. In particular,
|
||||||
|
// this means that surface and event callbacks should be safe
|
||||||
|
// after this point.
|
||||||
|
try {
|
||||||
|
scummvm.waitUntilRunning();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Log.e(this.toString(),
|
||||||
|
"Interrupted while waiting for ScummVM.initBackend", e);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
scummvm.setSurface(main_surface.getHolder());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Runs in another thread
|
||||||
|
private void runScummVM() throws IOException {
|
||||||
|
getFilesDir().mkdirs();
|
||||||
|
String[] args = {
|
||||||
|
"ScummVM-lib",
|
||||||
|
"--config=" + getFileStreamPath("scummvmrc").getPath(),
|
||||||
|
"--path=" + Environment.getExternalStorageDirectory().getPath(),
|
||||||
|
"--gui-theme=scummmodern",
|
||||||
|
"--savepath=" + getDir("saves", 0).getPath(),
|
||||||
|
};
|
||||||
|
|
||||||
|
int ret = scummvm.scummVMMain(args);
|
||||||
|
|
||||||
|
// On exit, tear everything down for a fresh
|
||||||
|
// restart next time.
|
||||||
|
System.exit(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean was_paused = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
if (scummvm != null) {
|
||||||
|
was_paused = true;
|
||||||
|
scummvm.pause();
|
||||||
|
}
|
||||||
|
super.onPause();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
if (scummvm != null && was_paused)
|
||||||
|
scummvm.resume();
|
||||||
|
was_paused = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStop() {
|
||||||
|
if (scummvm != null) {
|
||||||
|
scummvm.pushEvent(new Event(Event.EVENT_QUIT));
|
||||||
|
try {
|
||||||
|
scummvm_thread.join(1000); // 1s timeout
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Log.i(this.toString(),
|
||||||
|
"Error while joining ScummVM thread", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.onStop();
|
||||||
|
}
|
||||||
|
|
||||||
|
static final int MSG_MENU_LONG_PRESS = 1;
|
||||||
|
private final Handler keycodeMenuTimeoutHandler = new Handler() {
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message msg) {
|
||||||
|
if (msg.what == MSG_MENU_LONG_PRESS) {
|
||||||
|
InputMethodManager imm = (InputMethodManager)
|
||||||
|
getSystemService(INPUT_METHOD_SERVICE);
|
||||||
|
if (imm != null)
|
||||||
|
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onKeyUp(int keyCode, KeyEvent kevent) {
|
||||||
|
return onKeyDown(keyCode, kevent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onKeyMultiple(int keyCode, int repeatCount,
|
||||||
|
KeyEvent kevent) {
|
||||||
|
return onKeyDown(keyCode, kevent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onKeyDown(int keyCode, KeyEvent kevent) {
|
||||||
|
// Filter out "special" keys
|
||||||
|
switch (keyCode) {
|
||||||
|
case KeyEvent.KEYCODE_MENU:
|
||||||
|
// 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 (kevent.getRepeatCount() > 0)
|
||||||
|
// Ignore keyrepeat for menu
|
||||||
|
return false;
|
||||||
|
boolean timeout_fired = false;
|
||||||
|
if (getResources().getConfiguration().keyboard ==
|
||||||
|
Configuration.KEYBOARD_NOKEYS) {
|
||||||
|
timeout_fired = !keycodeMenuTimeoutHandler.hasMessages(MSG_MENU_LONG_PRESS);
|
||||||
|
keycodeMenuTimeoutHandler.removeMessages(MSG_MENU_LONG_PRESS);
|
||||||
|
if (kevent.getAction() == KeyEvent.ACTION_DOWN) {
|
||||||
|
keycodeMenuTimeoutHandler.sendMessageDelayed(
|
||||||
|
keycodeMenuTimeoutHandler.obtainMessage(MSG_MENU_LONG_PRESS),
|
||||||
|
ViewConfiguration.getLongPressTimeout());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (kevent.getAction() == KeyEvent.ACTION_UP) {
|
||||||
|
if (!timeout_fired)
|
||||||
|
scummvm.pushEvent(new Event(Event.EVENT_MAINMENU));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
case KeyEvent.KEYCODE_CAMERA:
|
||||||
|
case KeyEvent.KEYCODE_SEARCH:
|
||||||
|
_do_right_click = (kevent.getAction() == KeyEvent.ACTION_DOWN);
|
||||||
|
return true;
|
||||||
|
case KeyEvent.KEYCODE_DPAD_CENTER:
|
||||||
|
case KeyEvent.KEYCODE_DPAD_UP:
|
||||||
|
case KeyEvent.KEYCODE_DPAD_DOWN:
|
||||||
|
case KeyEvent.KEYCODE_DPAD_LEFT:
|
||||||
|
case KeyEvent.KEYCODE_DPAD_RIGHT: {
|
||||||
|
// HTC Hero doesn't seem to generate
|
||||||
|
// MotionEvent.ACTION_DOWN events on trackball press :(
|
||||||
|
// We'll have to just fake one here.
|
||||||
|
// Some other handsets lack a trackball, so the DPAD is
|
||||||
|
// the only way of moving the cursor.
|
||||||
|
int motion_action;
|
||||||
|
// FIXME: this logic is a mess.
|
||||||
|
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
|
||||||
|
switch (kevent.getAction()) {
|
||||||
|
case KeyEvent.ACTION_DOWN:
|
||||||
|
motion_action = MotionEvent.ACTION_DOWN;
|
||||||
|
break;
|
||||||
|
case KeyEvent.ACTION_UP:
|
||||||
|
motion_action = MotionEvent.ACTION_UP;
|
||||||
|
break;
|
||||||
|
default: // ACTION_MULTIPLE
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
motion_action = MotionEvent.ACTION_MOVE;
|
||||||
|
|
||||||
|
Event e = new Event(getEventType(motion_action));
|
||||||
|
e.mouse_x = 0;
|
||||||
|
e.mouse_y = 0;
|
||||||
|
e.mouse_relative = true;
|
||||||
|
switch (keyCode) {
|
||||||
|
case KeyEvent.KEYCODE_DPAD_UP:
|
||||||
|
e.mouse_y = -TRACKBALL_SCALE;
|
||||||
|
break;
|
||||||
|
case KeyEvent.KEYCODE_DPAD_DOWN:
|
||||||
|
e.mouse_y = TRACKBALL_SCALE;
|
||||||
|
break;
|
||||||
|
case KeyEvent.KEYCODE_DPAD_LEFT:
|
||||||
|
e.mouse_x = -TRACKBALL_SCALE;
|
||||||
|
break;
|
||||||
|
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
||||||
|
e.mouse_x = TRACKBALL_SCALE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
scummvm.pushEvent(e);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case KeyEvent.KEYCODE_BACK:
|
||||||
|
// skip isSystem() check and fall through to main code
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (kevent.isSystem())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: what do I need to do for composed characters?
|
||||||
|
|
||||||
|
Event e = new Event();
|
||||||
|
|
||||||
|
switch (kevent.getAction()) {
|
||||||
|
case KeyEvent.ACTION_DOWN:
|
||||||
|
e.type = Event.EVENT_KEYDOWN;
|
||||||
|
e.synthetic = false;
|
||||||
|
break;
|
||||||
|
case KeyEvent.ACTION_UP:
|
||||||
|
e.type = Event.EVENT_KEYUP;
|
||||||
|
e.synthetic = false;
|
||||||
|
break;
|
||||||
|
case KeyEvent.ACTION_MULTIPLE:
|
||||||
|
// e.type is handled below
|
||||||
|
e.synthetic = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
e.kbd_keycode = Event.androidKeyMap.containsKey(keyCode) ?
|
||||||
|
Event.androidKeyMap.get(keyCode) : Event.KEYCODE_INVALID;
|
||||||
|
e.kbd_ascii = kevent.getUnicodeChar();
|
||||||
|
if (e.kbd_ascii == 0)
|
||||||
|
e.kbd_ascii = e.kbd_keycode; // scummvm keycodes are mostly ascii
|
||||||
|
|
||||||
|
|
||||||
|
e.kbd_flags = 0;
|
||||||
|
if (kevent.isAltPressed())
|
||||||
|
e.kbd_flags |= Event.KBD_ALT;
|
||||||
|
if (kevent.isSymPressed()) // no ctrl key in android, so use sym (?)
|
||||||
|
e.kbd_flags |= Event.KBD_CTRL;
|
||||||
|
if (kevent.isShiftPressed()) {
|
||||||
|
if (keyCode >= KeyEvent.KEYCODE_0 &&
|
||||||
|
keyCode <= KeyEvent.KEYCODE_9) {
|
||||||
|
// Shift+number -> convert to F* key
|
||||||
|
int offset = keyCode == KeyEvent.KEYCODE_0 ?
|
||||||
|
10 : keyCode - KeyEvent.KEYCODE_1; // turn 0 into 10
|
||||||
|
e.kbd_keycode = Event.KEYCODE_F1 + offset;
|
||||||
|
e.kbd_ascii = Event.ASCII_F1 + offset;
|
||||||
|
} else
|
||||||
|
e.kbd_flags |= Event.KBD_SHIFT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kevent.getAction() == KeyEvent.ACTION_MULTIPLE) {
|
||||||
|
for (int i = 0; i <= kevent.getRepeatCount(); i++) {
|
||||||
|
e.type = Event.EVENT_KEYDOWN;
|
||||||
|
scummvm.pushEvent(e);
|
||||||
|
e.type = Event.EVENT_KEYUP;
|
||||||
|
scummvm.pushEvent(e);
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
scummvm.pushEvent(e);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getEventType(int action) {
|
||||||
|
switch (action) {
|
||||||
|
case MotionEvent.ACTION_DOWN:
|
||||||
|
_last_click_was_right = _do_right_click;
|
||||||
|
return _last_click_was_right ?
|
||||||
|
Event.EVENT_RBUTTONDOWN : Event.EVENT_LBUTTONDOWN;
|
||||||
|
case MotionEvent.ACTION_UP:
|
||||||
|
return _last_click_was_right ?
|
||||||
|
Event.EVENT_RBUTTONUP : Event.EVENT_LBUTTONUP;
|
||||||
|
case MotionEvent.ACTION_MOVE:
|
||||||
|
return Event.EVENT_MOUSEMOVE;
|
||||||
|
default:
|
||||||
|
return Event.EVENT_INVALID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onTrackballEvent(MotionEvent event) {
|
||||||
|
int type = getEventType(event.getAction());
|
||||||
|
if (type == Event.EVENT_INVALID)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Event e = new Event(type);
|
||||||
|
e.mouse_x =
|
||||||
|
(int)(event.getX() * event.getXPrecision()) * TRACKBALL_SCALE;
|
||||||
|
e.mouse_y =
|
||||||
|
(int)(event.getY() * event.getYPrecision()) * TRACKBALL_SCALE;
|
||||||
|
e.mouse_relative = true;
|
||||||
|
scummvm.pushEvent(e);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onTouchEvent(MotionEvent event) {
|
||||||
|
int type = getEventType(event.getAction());
|
||||||
|
if (type == Event.EVENT_INVALID)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Event e = new Event(type);
|
||||||
|
e.mouse_x = (int)event.getX();
|
||||||
|
e.mouse_y = (int)event.getY();
|
||||||
|
e.mouse_relative = false;
|
||||||
|
scummvm.pushEvent(e);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,29 @@
|
||||||
|
package org.inodes.gus.scummvm;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class ScummVMApplication extends Application {
|
||||||
|
public final static String ACTION_PLUGIN_QUERY = "org.inodes.gus.scummvm.action.PLUGIN_QUERY";
|
||||||
|
public final static String EXTRA_UNPACK_LIBS = "org.inodes.gus.scummvm.extra.UNPACK_LIBS";
|
||||||
|
|
||||||
|
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(),
|
||||||
|
// "/.ScummVM.tmp");
|
||||||
|
// This is owned by download manager and requires special
|
||||||
|
// permissions to access :(
|
||||||
|
//cache_dir = Environment.getDownloadCacheDirectory();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static File getLastCacheDir() {
|
||||||
|
return cache_dir;
|
||||||
|
}
|
||||||
|
}
|
370
backends/platform/android/org/inodes/gus/scummvm/Unpacker.java
Normal file
370
backends/platform/android/org/inodes/gus/scummvm/Unpacker.java
Normal file
|
@ -0,0 +1,370 @@
|
||||||
|
package org.inodes.gus.scummvm;
|
||||||
|
|
||||||
|
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.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 {
|
||||||
|
private final static String META_NEXT_ACTIVITY =
|
||||||
|
"org.inodes.gus.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;
|
||||||
|
|
||||||
|
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.setPackage(origIntent.getPackage());
|
||||||
|
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(this.toString(),
|
||||||
|
"Starting next activity with intent " + intent);
|
||||||
|
startActivity(intent);
|
||||||
|
} else {
|
||||||
|
Log.w(this.toString(),
|
||||||
|
"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(this.toString(), "Package " + pkg +
|
||||||
|
" not found", e);
|
||||||
|
continue;
|
||||||
|
} catch (IOException e) {
|
||||||
|
// FIXME: show some sort of GUI error dialog
|
||||||
|
Log.e(this.toString(),
|
||||||
|
"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(this.toString(),
|
||||||
|
"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(this.toString(),
|
||||||
|
"Replacing " + dest.getPath() +
|
||||||
|
" old.mtime=" + dest.lastModified() +
|
||||||
|
" new.mtime=" + zipentry.getTime() +
|
||||||
|
" old.size=" + dest.length() +
|
||||||
|
" new.size=" + zipentry.getSize());
|
||||||
|
else
|
||||||
|
Log.i(this.toString(),
|
||||||
|
"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(this.toString(), "Error unpacking plugin", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (progress != total_size)
|
||||||
|
Log.d(this.toString(), "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(ScummVMApplication.ACTION_PLUGIN_QUERY)) {
|
||||||
|
Log.e(this.toString(),
|
||||||
|
"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(ScummVMApplication.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 ScummVM code (not really a "plugin" as such)
|
||||||
|
unpack_libs.add(new Uri.Builder()
|
||||||
|
.scheme("plugin")
|
||||||
|
.authority(getPackageName())
|
||||||
|
.path("mylib/armeabi/libscummvm.so")
|
||||||
|
.toString());
|
||||||
|
extras.putStringArrayList(ScummVMApplication.EXTRA_UNPACK_LIBS,
|
||||||
|
unpack_libs);
|
||||||
|
|
||||||
|
Intent intent = new Intent(ScummVMApplication.ACTION_PLUGIN_QUERY);
|
||||||
|
sendOrderedBroadcast(intent, Manifest.permission.SCUMMVM_PLUGIN,
|
||||||
|
new PluginBroadcastReciever(),
|
||||||
|
null, RESULT_OK, null, extras);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle b) {
|
||||||
|
super.onCreate(b);
|
||||||
|
|
||||||
|
mUnpackDest = ScummVMApplication.getLastCacheDir();
|
||||||
|
|
||||||
|
setContentView(R.layout.splash);
|
||||||
|
mProgress = (ProgressBar)findViewById(R.id.progress);
|
||||||
|
|
||||||
|
setResult(RESULT_CANCELED);
|
||||||
|
|
||||||
|
tryUnpack();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryUnpack() {
|
||||||
|
Intent intent = new Intent(ScummVMApplication.ACTION_PLUGIN_QUERY);
|
||||||
|
List<ResolveInfo> plugins = getPackageManager()
|
||||||
|
.queryBroadcastReceivers(intent, 0);
|
||||||
|
if (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=ScummVM 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(this.toString(),
|
||||||
|
"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(this.toString(), "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(this.toString(), "Unable to find my own meta-data", e);
|
||||||
|
return new Bundle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
135
backends/platform/android/scummvm-android-themeengine.patch
Normal file
135
backends/platform/android/scummvm-android-themeengine.patch
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
diff -r 884e66fd1b9c gui/ThemeEngine.cpp
|
||||||
|
--- a/gui/ThemeEngine.cpp Tue Apr 13 09:30:52 2010 +1000
|
||||||
|
+++ b/gui/ThemeEngine.cpp Fri May 28 23:24:43 2010 +1000
|
||||||
|
@@ -390,21 +390,19 @@
|
||||||
|
|
||||||
|
// Try to create a Common::Archive with the files of the theme.
|
||||||
|
if (!_themeArchive && !_themeFile.empty()) {
|
||||||
|
- Common::FSNode node(_themeFile);
|
||||||
|
- if (node.getName().hasSuffix(".zip") && !node.isDirectory()) {
|
||||||
|
+ Common::ArchiveMemberPtr member = SearchMan.getMember(_themeFile);
|
||||||
|
+ if (member && member->getName().hasSuffix(".zip")) {
|
||||||
|
#ifdef USE_ZLIB
|
||||||
|
- Common::Archive *zipArchive = Common::makeZipArchive(node);
|
||||||
|
+ Common::Archive *zipArchive = Common::makeZipArchive(member->createReadStream());
|
||||||
|
|
||||||
|
if (!zipArchive) {
|
||||||
|
- warning("Failed to open Zip archive '%s'.", node.getPath().c_str());
|
||||||
|
+ warning("Failed to open Zip archive '%s'.", member->getDisplayName().c_str());
|
||||||
|
}
|
||||||
|
_themeArchive = zipArchive;
|
||||||
|
#else
|
||||||
|
warning("Trying to load theme '%s' in a Zip archive without zLib support", _themeFile.c_str());
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
- } else if (node.isDirectory()) {
|
||||||
|
- _themeArchive = new Common::FSDirectory(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -1436,6 +1434,30 @@
|
||||||
|
return tok.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
+bool ThemeEngine::themeConfigUsable(const Common::ArchiveMember &member, Common::String &themeName) {
|
||||||
|
+ Common::File stream;
|
||||||
|
+ bool foundHeader = false;
|
||||||
|
+
|
||||||
|
+ if (member.getName().hasSuffix(".zip")) {
|
||||||
|
+#ifdef USE_ZLIB
|
||||||
|
+ Common::Archive *zipArchive = Common::makeZipArchive(member.createReadStream());
|
||||||
|
+
|
||||||
|
+ if (zipArchive && zipArchive->hasFile("THEMERC")) {
|
||||||
|
+ stream.open("THEMERC", *zipArchive);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ delete zipArchive;
|
||||||
|
+#endif
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if (stream.isOpen()) {
|
||||||
|
+ Common::String stxHeader = stream.readLine();
|
||||||
|
+ foundHeader = themeConfigParseHeader(stxHeader, themeName);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ return foundHeader;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
bool ThemeEngine::themeConfigUsable(const Common::FSNode &node, Common::String &themeName) {
|
||||||
|
Common::File stream;
|
||||||
|
bool foundHeader = false;
|
||||||
|
@@ -1493,10 +1515,6 @@
|
||||||
|
if (ConfMan.hasKey("themepath"))
|
||||||
|
listUsableThemes(Common::FSNode(ConfMan.get("themepath")), list);
|
||||||
|
|
||||||
|
-#ifdef DATA_PATH
|
||||||
|
- listUsableThemes(Common::FSNode(DATA_PATH), list);
|
||||||
|
-#endif
|
||||||
|
-
|
||||||
|
#if defined(MACOSX) || defined(IPHONE)
|
||||||
|
CFURLRef resourceUrl = CFBundleCopyResourcesDirectoryURL(CFBundleGetMainBundle());
|
||||||
|
if (resourceUrl) {
|
||||||
|
@@ -1509,10 +1527,7 @@
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
- if (ConfMan.hasKey("extrapath"))
|
||||||
|
- listUsableThemes(Common::FSNode(ConfMan.get("extrapath")), list);
|
||||||
|
-
|
||||||
|
- listUsableThemes(Common::FSNode("."), list, 1);
|
||||||
|
+ listUsableThemes(SearchMan, list);
|
||||||
|
|
||||||
|
// Now we need to strip all duplicates
|
||||||
|
// TODO: It might not be the best idea to strip duplicates. The user might
|
||||||
|
@@ -1531,6 +1546,34 @@
|
||||||
|
output.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
+void ThemeEngine::listUsableThemes(Common::Archive &archive, Common::List<ThemeDescriptor> &list) {
|
||||||
|
+ ThemeDescriptor td;
|
||||||
|
+
|
||||||
|
+#ifdef USE_ZLIB
|
||||||
|
+ Common::ArchiveMemberList fileList;
|
||||||
|
+ archive.listMatchingMembers(fileList, "*.zip");
|
||||||
|
+ for (Common::ArchiveMemberList::iterator i = fileList.begin();
|
||||||
|
+ i != fileList.end(); ++i) {
|
||||||
|
+ td.name.clear();
|
||||||
|
+ if (themeConfigUsable(**i, td.name)) {
|
||||||
|
+ td.filename = (*i)->getName();
|
||||||
|
+ td.id = (*i)->getDisplayName();
|
||||||
|
+
|
||||||
|
+ // If the name of the node object also contains
|
||||||
|
+ // the ".zip" suffix, we will strip it.
|
||||||
|
+ if (td.id.hasSuffix(".zip")) {
|
||||||
|
+ for (int j = 0; j < 4; ++j)
|
||||||
|
+ td.id.deleteLastChar();
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ list.push_back(td);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ fileList.clear();
|
||||||
|
+#endif
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
void ThemeEngine::listUsableThemes(const Common::FSNode &node, Common::List<ThemeDescriptor> &list, int depth) {
|
||||||
|
if (!node.exists() || !node.isReadable() || !node.isDirectory())
|
||||||
|
return;
|
||||||
|
diff -r 884e66fd1b9c gui/ThemeEngine.h
|
||||||
|
--- a/gui/ThemeEngine.h Tue Apr 13 09:30:52 2010 +1000
|
||||||
|
+++ b/gui/ThemeEngine.h Fri May 28 23:24:43 2010 +1000
|
||||||
|
@@ -560,11 +560,13 @@
|
||||||
|
static void listUsableThemes(Common::List<ThemeDescriptor> &list);
|
||||||
|
private:
|
||||||
|
static bool themeConfigUsable(const Common::FSNode &node, Common::String &themeName);
|
||||||
|
+ static bool themeConfigUsable(const Common::ArchiveMember &member, Common::String &themeName);
|
||||||
|
static bool themeConfigParseHeader(Common::String header, Common::String &themeName);
|
||||||
|
|
||||||
|
static Common::String getThemeFile(const Common::String &id);
|
||||||
|
static Common::String getThemeId(const Common::String &filename);
|
||||||
|
static void listUsableThemes(const Common::FSNode &node, Common::List<ThemeDescriptor> &list, int depth = -1);
|
||||||
|
+ static void listUsableThemes(Common::Archive &archive, Common::List<ThemeDescriptor> &list);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
OSystem *_system; /** Global system object. */
|
|
@ -51,7 +51,7 @@ static const char USAGE_STRING[] =
|
||||||
;
|
;
|
||||||
|
|
||||||
// DONT FIXME: DO NOT ORDER ALPHABETICALLY, THIS IS ORDERED BY IMPORTANCE/CATEGORY! :)
|
// DONT FIXME: DO NOT ORDER ALPHABETICALLY, THIS IS ORDERED BY IMPORTANCE/CATEGORY! :)
|
||||||
#if defined(PALMOS_MODE) || defined(__SYMBIAN32__) || defined(__GP32__)
|
#if defined(PALMOS_MODE) || defined(__SYMBIAN32__) || defined(__GP32__) || defined(ANDROID)
|
||||||
static const char HELP_STRING[] = "NoUsageString"; // save more data segment space
|
static const char HELP_STRING[] = "NoUsageString"; // save more data segment space
|
||||||
#else
|
#else
|
||||||
static const char HELP_STRING[] =
|
static const char HELP_STRING[] =
|
||||||
|
@ -948,7 +948,7 @@ Common::Error processSettings(Common::String &command, Common::StringMap &settin
|
||||||
// environment variable. This is weaker than a --savepath on the
|
// environment variable. This is weaker than a --savepath on the
|
||||||
// command line, but overrides the default savepath, hence it is
|
// command line, but overrides the default savepath, hence it is
|
||||||
// handled here, just before the command line gets parsed.
|
// handled here, just before the command line gets parsed.
|
||||||
#if !defined(MACOS_CARBON) && !defined(_WIN32_WCE) && !defined(PALMOS_MODE) && !defined(__GP32__)
|
#if !defined(MACOS_CARBON) && !defined(_WIN32_WCE) && !defined(PALMOS_MODE) && !defined(__GP32__) && !defined(ANDROID)
|
||||||
if (!settings.contains("savepath")) {
|
if (!settings.contains("savepath")) {
|
||||||
const char *dir = getenv("SCUMMVM_SAVEPATH");
|
const char *dir = getenv("SCUMMVM_SAVEPATH");
|
||||||
if (dir && *dir && strlen(dir) < MAXPATHLEN) {
|
if (dir && *dir && strlen(dir) < MAXPATHLEN) {
|
||||||
|
|
|
@ -43,6 +43,10 @@ extern bool isSmartphone();
|
||||||
#define fputs(str, file) DS::std_fwrite(str, strlen(str), 1, file)
|
#define fputs(str, file) DS::std_fwrite(str, strlen(str), 1, file)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef ANDROID
|
||||||
|
#include <android/log.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace Common {
|
namespace Common {
|
||||||
|
|
||||||
static OutputFormatter s_errorOutputFormatter = 0;
|
static OutputFormatter s_errorOutputFormatter = 0;
|
||||||
|
@ -71,7 +75,9 @@ void warning(const char *s, ...) {
|
||||||
vsnprintf(buf, STRINGBUFLEN, s, va);
|
vsnprintf(buf, STRINGBUFLEN, s, va);
|
||||||
va_end(va);
|
va_end(va);
|
||||||
|
|
||||||
#if !defined (__SYMBIAN32__)
|
#if defined( ANDROID )
|
||||||
|
__android_log_write(ANDROID_LOG_WARN, "ScummVM", buf);
|
||||||
|
#elif !defined (__SYMBIAN32__)
|
||||||
fputs("WARNING: ", stderr);
|
fputs("WARNING: ", stderr);
|
||||||
fputs(buf, stderr);
|
fputs(buf, stderr);
|
||||||
fputs("!\n", stderr);
|
fputs("!\n", stderr);
|
||||||
|
@ -141,6 +147,10 @@ void NORETURN_PRE error(const char *s, ...) {
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef ANDROID
|
||||||
|
__android_log_assert("Fatal error", "ScummVM", "%s", buf_output);
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef PALMOS_MODE
|
#ifdef PALMOS_MODE
|
||||||
extern void PalmFatalError(const char *err);
|
extern void PalmFatalError(const char *err);
|
||||||
PalmFatalError(buf_output);
|
PalmFatalError(buf_output);
|
||||||
|
|
43
configure
vendored
43
configure
vendored
|
@ -999,6 +999,11 @@ wince)
|
||||||
_host_cpu=arm
|
_host_cpu=arm
|
||||||
_host_alias=arm-wince-mingw32ce
|
_host_alias=arm-wince-mingw32ce
|
||||||
;;
|
;;
|
||||||
|
android)
|
||||||
|
_host_os=android
|
||||||
|
_host_cpu=arm
|
||||||
|
_host_alias=arm-android-eabi
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
if test -n "$_host"; then
|
if test -n "$_host"; then
|
||||||
guessed_host=`$_srcdir/config.sub $_host`
|
guessed_host=`$_srcdir/config.sub $_host`
|
||||||
|
@ -1077,6 +1082,12 @@ psp)
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
|
android)
|
||||||
|
if test -z "$ANDROID_SDK"; then
|
||||||
|
echo "Please set ANDROID_SDK in your environment. export ANDROID_SDK=<path to Android SDK>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
@ -1399,6 +1410,11 @@ case $_host_os in
|
||||||
DEFINES="$DEFINES -D_WIN32_WCE=300 -D__ARM__ -D_ARM_ -DUNICODE -DFPM_DEFAULT -DNONSTANDARD_PORT"
|
DEFINES="$DEFINES -D_WIN32_WCE=300 -D__ARM__ -D_ARM_ -DUNICODE -DFPM_DEFAULT -DNONSTANDARD_PORT"
|
||||||
DEFINES="$DEFINES -DWIN32 -Dcdecl= -D__cdecl__="
|
DEFINES="$DEFINES -DWIN32 -Dcdecl= -D__cdecl__="
|
||||||
;;
|
;;
|
||||||
|
android)
|
||||||
|
DEFINES="$DEFINES -DUNIX"
|
||||||
|
CXXFLAGS="$CXXFLAGS -Os -msoft-float -mtune=xscale -march=armv5te -D__ARM_ARCH_5__ -D__ARM_ARCH_5T__ -D__ARM_ARCH_5TE__"
|
||||||
|
add_line_to_config_mk "ANDROID_SDK = $ANDROID_SDK"
|
||||||
|
;;
|
||||||
# given this is a shell script assume some type of unix
|
# given this is a shell script assume some type of unix
|
||||||
*)
|
*)
|
||||||
echo "WARNING: could not establish system type, assuming unix like"
|
echo "WARNING: could not establish system type, assuming unix like"
|
||||||
|
@ -1647,6 +1663,19 @@ if test -n "$_host"; then
|
||||||
_mt32emu="no"
|
_mt32emu="no"
|
||||||
_port_mk="backends/platform/wince/wince.mk"
|
_port_mk="backends/platform/wince/wince.mk"
|
||||||
;;
|
;;
|
||||||
|
android)
|
||||||
|
DEFINES="$DEFINES -DANDROID -DUNIX -DUSE_ARM_SMUSH_ASM"
|
||||||
|
_endian=little
|
||||||
|
_need_memalign=yes
|
||||||
|
add_line_to_config_mk 'USE_ARM_SOUND_ASM = 1'
|
||||||
|
add_line_to_config_mk 'USE_ARM_SMUSH_ASM = 1'
|
||||||
|
add_line_to_config_mk 'USE_ARM_GFX_ASM = 1'
|
||||||
|
add_line_to_config_mk 'USE_ARM_SCALER_ASM = 1'
|
||||||
|
add_line_to_config_mk 'USE_ARM_COSTUME_ASM = 1'
|
||||||
|
_backend="android"
|
||||||
|
_port_mk="backends/platform/android/android.mk"
|
||||||
|
_build_hq_scalers="no"
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
echo "WARNING: Unknown target, continuing with auto-detected values"
|
echo "WARNING: Unknown target, continuing with auto-detected values"
|
||||||
;;
|
;;
|
||||||
|
@ -1825,7 +1854,7 @@ POST_OBJS_FLAGS := -Wl,-no-whole-archive
|
||||||
LIBS += -ldl
|
LIBS += -ldl
|
||||||
'
|
'
|
||||||
;;
|
;;
|
||||||
linux*)
|
linux*|android)
|
||||||
_def_plugin='
|
_def_plugin='
|
||||||
#define PLUGIN_PREFIX "lib"
|
#define PLUGIN_PREFIX "lib"
|
||||||
#define PLUGIN_SUFFIX ".so"
|
#define PLUGIN_SUFFIX ".so"
|
||||||
|
@ -2432,6 +2461,14 @@ case $_backend in
|
||||||
INCLUDES="$INCLUDES "'-I$(srcdir) -I$(srcdir)/backends/platform/wince -I$(srcdir)/engines -I$(srcdir)/backends/platform/wince/missing/gcc -I$(srcdir)/backends/platform/wince/CEgui -I$(srcdir)/backends/platform/wince/CEkeys'
|
INCLUDES="$INCLUDES "'-I$(srcdir) -I$(srcdir)/backends/platform/wince -I$(srcdir)/engines -I$(srcdir)/backends/platform/wince/missing/gcc -I$(srcdir)/backends/platform/wince/CEgui -I$(srcdir)/backends/platform/wince/CEkeys'
|
||||||
LIBS="$LIBS -static -lSDL"
|
LIBS="$LIBS -static -lSDL"
|
||||||
;;
|
;;
|
||||||
|
android)
|
||||||
|
# -lgcc is carefully placed here - we want to catch
|
||||||
|
# all toolchain symbols in *our* libraries rather
|
||||||
|
# than pick up anything unhygenic from the Android libs.
|
||||||
|
LIBS="$LIBS -lgcc -lstdc++ -llog -lGLESv1_CM -lEGL"
|
||||||
|
DEFINES="$DEFINES -D__ANDROID__ -DANDROID_BACKEND -DREDUCE_MEMORY_USAGE"
|
||||||
|
add_line_to_config_mk 'PLUGIN_LDFLAGS += $(LDFLAGS) -Wl,-shared,-Bsymbolic'
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
echo "support for $_backend backend not implemented in configure script yet"
|
echo "support for $_backend backend not implemented in configure script yet"
|
||||||
exit 1
|
exit 1
|
||||||
|
@ -2447,7 +2484,7 @@ if test "$have_gcc" = yes ; then
|
||||||
case $_host_os in
|
case $_host_os in
|
||||||
# newlib-based system include files suppress non-C89 function
|
# newlib-based system include files suppress non-C89 function
|
||||||
# declarations under __STRICT_ANSI__
|
# declarations under __STRICT_ANSI__
|
||||||
mingw* | dreamcast | wii | gamecube | psp | wince | amigaos*)
|
mingw* | dreamcast | wii | gamecube | psp | wince | amigaos* | android)
|
||||||
CXXFLAGS="$CXXFLAGS -W -Wno-unused-parameter"
|
CXXFLAGS="$CXXFLAGS -W -Wno-unused-parameter"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
|
@ -2468,7 +2505,7 @@ fi;
|
||||||
|
|
||||||
# Some platforms use certain GNU extensions in header files
|
# Some platforms use certain GNU extensions in header files
|
||||||
case $_host_os in
|
case $_host_os in
|
||||||
gamecube | psp | wii)
|
gamecube | psp | wii | android)
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
CXXFLAGS="$CXXFLAGS -pedantic"
|
CXXFLAGS="$CXXFLAGS -pedantic"
|
||||||
|
|
169
dists/android/mkmanifest.pl
Normal file
169
dists/android/mkmanifest.pl
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
use File::Basename qw(dirname);
|
||||||
|
use File::Path qw(mkpath);
|
||||||
|
use IO::File;
|
||||||
|
use XML::Writer;
|
||||||
|
use XML::Parser;
|
||||||
|
use Getopt::Long;
|
||||||
|
|
||||||
|
use warnings;
|
||||||
|
use strict;
|
||||||
|
|
||||||
|
use constant ANDROID => 'http://schemas.android.com/apk/res/android';
|
||||||
|
|
||||||
|
my $id;
|
||||||
|
my $package_versionName;
|
||||||
|
my $package_versionCode;
|
||||||
|
my $configure = 'configure';
|
||||||
|
my $stringres = 'res/string/values.xml';
|
||||||
|
my $manifest = 'AndroidManifest.xml';
|
||||||
|
my $master_manifest;
|
||||||
|
my @unpack_libs;
|
||||||
|
GetOptions('id=s' => \$id,
|
||||||
|
'version-name=s' => \$package_versionName,
|
||||||
|
'version-code=i' => \$package_versionCode,
|
||||||
|
'configure=s' => \$configure,
|
||||||
|
'stringres=s' => \$stringres,
|
||||||
|
'manifest=s' => \$manifest,
|
||||||
|
'master-manifest=s' => \$master_manifest,
|
||||||
|
'unpacklib=s' => \@unpack_libs,
|
||||||
|
) or die;
|
||||||
|
die "Missing required arg"
|
||||||
|
unless $id and $package_versionName and $package_versionCode;
|
||||||
|
|
||||||
|
|
||||||
|
sub grope_engine_info {
|
||||||
|
my $configure = shift;
|
||||||
|
my @ret;
|
||||||
|
while (<$configure>) {
|
||||||
|
m/^add_engine \s+ (\w+) \s+ "(.*?)" \s+ \w+ (?:\s+ "([\w\s]*)")?/x
|
||||||
|
or next;
|
||||||
|
my $subengines = $3 || '';
|
||||||
|
my %info = (id => $1, name => $2,
|
||||||
|
subengines => [split / /, $subengines]);
|
||||||
|
push @ret, \%info;
|
||||||
|
}
|
||||||
|
return @ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub read_constraints {
|
||||||
|
my $manifest = shift;
|
||||||
|
my @constraints;
|
||||||
|
my $parser = new XML::Parser Handlers => {
|
||||||
|
Start => sub {
|
||||||
|
my $expat = shift;
|
||||||
|
my $elem = shift;
|
||||||
|
return if $elem !~
|
||||||
|
/^(uses-configuration|supports-screens|uses-sdk)$/;
|
||||||
|
my @constraint = ($elem);
|
||||||
|
while (@_) {
|
||||||
|
my $attr = shift;
|
||||||
|
my $value = shift;
|
||||||
|
$attr = [ANDROID, $attr] if $attr =~ s/^android://;
|
||||||
|
push @constraint, $attr, $value;
|
||||||
|
}
|
||||||
|
push @constraints, \@constraint;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
$parser->parse($manifest);
|
||||||
|
return @constraints;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub print_stringres {
|
||||||
|
my $output = shift;
|
||||||
|
my $info = shift;
|
||||||
|
|
||||||
|
my $writer = new XML::Writer(OUTPUT => $output, ENCODING => 'utf-8',
|
||||||
|
DATA_MODE => 1, DATA_INDENT => 2);
|
||||||
|
|
||||||
|
$writer->xmlDecl();
|
||||||
|
$writer->startTag('resources');
|
||||||
|
|
||||||
|
while (my ($k,$v) = each %$info) {
|
||||||
|
$writer->dataElement('string', $v, name => $k);
|
||||||
|
}
|
||||||
|
|
||||||
|
$writer->endTag('resources');
|
||||||
|
$writer->end();
|
||||||
|
}
|
||||||
|
|
||||||
|
sub print_manifest {
|
||||||
|
my $output = shift;
|
||||||
|
my $info = shift;
|
||||||
|
my $constraints = shift;
|
||||||
|
|
||||||
|
my $writer = new XML::Writer(OUTPUT => $output, ENCODING => 'utf-8',
|
||||||
|
DATA_MODE => 1, DATA_INDENT => 2,
|
||||||
|
NAMESPACES => 1,
|
||||||
|
PREFIX_MAP => {ANDROID, 'android'});
|
||||||
|
|
||||||
|
$writer->xmlDecl();
|
||||||
|
|
||||||
|
$writer->startTag(
|
||||||
|
'manifest',
|
||||||
|
'package' => "org.inodes.gus.scummvm.plugin.$info->{name}",
|
||||||
|
[ANDROID, 'versionCode'] => $package_versionCode,
|
||||||
|
[ANDROID, 'versionName'] => $package_versionName,
|
||||||
|
);
|
||||||
|
|
||||||
|
$writer->startTag(
|
||||||
|
'application',
|
||||||
|
[ANDROID, 'label'] => '@string/app_name',
|
||||||
|
[ANDROID, 'description'] => '@string/app_desc',
|
||||||
|
[ANDROID, 'icon'] => '@drawable/scummvm',
|
||||||
|
);
|
||||||
|
|
||||||
|
$writer->startTag(
|
||||||
|
'receiver',
|
||||||
|
[ANDROID, 'name'] => 'org.inodes.gus.scummvm.PluginProvider',
|
||||||
|
[ANDROID, 'process'] => 'org.inodes.gus.scummvm');
|
||||||
|
|
||||||
|
$writer->startTag('intent-filter');
|
||||||
|
$writer->emptyTag('action', [ANDROID, 'name'] =>
|
||||||
|
'org.inodes.gus.scummvm.action.PLUGIN_QUERY');
|
||||||
|
$writer->emptyTag('category', [ANDROID, 'name'] =>
|
||||||
|
'android.intent.category.INFO');
|
||||||
|
$writer->endTag('intent-filter');
|
||||||
|
$writer->emptyTag(
|
||||||
|
'meta-data',
|
||||||
|
[ANDROID, 'name'] => 'org.inodes.gus.scummvm.meta.UNPACK_LIB',
|
||||||
|
[ANDROID, 'value'] => $_)
|
||||||
|
for @{$info->{unpack_libs}};
|
||||||
|
|
||||||
|
$writer->endTag('receiver');
|
||||||
|
$writer->endTag('application');
|
||||||
|
|
||||||
|
$writer->emptyTag('uses-permission', [ANDROID, 'name'] =>
|
||||||
|
'org.inodes.gus.scummvm.permission.SCUMMVM_PLUGIN');
|
||||||
|
|
||||||
|
$writer->emptyTag(@$_) foreach @$constraints;
|
||||||
|
|
||||||
|
$writer->endTag('manifest');
|
||||||
|
$writer->end();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
my %engines;
|
||||||
|
for my $engine (grope_engine_info(new IO::File $configure, 'r')) {
|
||||||
|
$engines{$engine->{id}} = $engine;
|
||||||
|
}
|
||||||
|
|
||||||
|
my @games = ($id, @{$engines{$id}{subengines}});
|
||||||
|
my $games_desc = join('; ', map $engines{$_}{name}, @games);
|
||||||
|
|
||||||
|
my @constraints = read_constraints(new IO::File $master_manifest, 'r');
|
||||||
|
|
||||||
|
print "Writing $stringres ...\n";
|
||||||
|
mkpath(dirname($stringres));
|
||||||
|
print_stringres(IO::File->new($stringres, 'w'),
|
||||||
|
{app_name => qq{ScummVM plugin: "$id"},
|
||||||
|
app_desc => "Game engine for: $games_desc",
|
||||||
|
});
|
||||||
|
|
||||||
|
print "Writing $manifest ...\n";
|
||||||
|
mkpath(dirname($manifest));
|
||||||
|
print_manifest(IO::File->new($manifest, 'w'),
|
||||||
|
{name => $id, unpack_libs => \@unpack_libs}, \@constraints);
|
||||||
|
|
||||||
|
exit 0;
|
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="#e9bb8b"
|
||||||
|
android:endColor="#d16e09"
|
||||||
|
android:angle="315" />
|
||||||
|
</shape>
|
10
dists/android/res/layout/main.xml
Normal file
10
dists/android/res/layout/main.xml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<org.inodes.gus.scummvm.EditableSurfaceView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="fill_parent" android:layout_height="fill_parent"
|
||||||
|
android:id="@+id/main_surface"
|
||||||
|
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/scummvm_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">ScummVM</string>
|
||||||
|
<string name="app_desc">Graphic adventure game engine</string>
|
||||||
|
<string name="quit">Quit</string>
|
||||||
|
<string name="scummvm_perm_plugin_label">ScummVM plugin</string>
|
||||||
|
<string name="scummvm_perm_plugin_desc">Allows the application to
|
||||||
|
provide a ScummVM loadable plugin: code that will be executed in the
|
||||||
|
ScummVM application. Malicious plugins may do anything ScummVM
|
||||||
|
itself could do: write to your SD card, delete your savegames,
|
||||||
|
change the ScummVM 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">ScummVM requires at least one <i>game
|
||||||
|
engine</i> to be useful. Engines are available as separate plugin
|
||||||
|
packages, from wherever you found ScummVM.</string>
|
||||||
|
<string name="to_market">To Market</string>
|
||||||
|
</resources>
|
|
@ -35,7 +35,7 @@
|
||||||
#include "sound/audiocd.h"
|
#include "sound/audiocd.h"
|
||||||
|
|
||||||
#ifdef USE_TREMOR
|
#ifdef USE_TREMOR
|
||||||
#ifdef __GP32__ // GP32 uses custom libtremor
|
#if defined(ANDROID) || defined(__GP32__) // custom libtremor locations
|
||||||
#include <ivorbisfile.h>
|
#include <ivorbisfile.h>
|
||||||
#else
|
#else
|
||||||
#include <tremor/ivorbisfile.h>
|
#include <tremor/ivorbisfile.h>
|
||||||
|
|
|
@ -40,6 +40,7 @@ my @subs_files = qw(
|
||||||
dists/iphone/Info.plist
|
dists/iphone/Info.plist
|
||||||
dists/irix/scummvm.spec
|
dists/irix/scummvm.spec
|
||||||
dists/wii/meta.xml
|
dists/wii/meta.xml
|
||||||
|
dists/android/AndroidManifest.xml
|
||||||
backends/platform/psp/README.PSP
|
backends/platform/psp/README.PSP
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue