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! :)
|
||||
#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
|
||||
#else
|
||||
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
|
||||
// command line, but overrides the default savepath, hence it is
|
||||
// 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")) {
|
||||
const char *dir = getenv("SCUMMVM_SAVEPATH");
|
||||
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)
|
||||
#endif
|
||||
|
||||
#ifdef ANDROID
|
||||
#include <android/log.h>
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
|
||||
static OutputFormatter s_errorOutputFormatter = 0;
|
||||
|
@ -71,7 +75,9 @@ void warning(const char *s, ...) {
|
|||
vsnprintf(buf, STRINGBUFLEN, s, 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(buf, stderr);
|
||||
fputs("!\n", stderr);
|
||||
|
@ -141,6 +147,10 @@ void NORETURN_PRE error(const char *s, ...) {
|
|||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef ANDROID
|
||||
__android_log_assert("Fatal error", "ScummVM", "%s", buf_output);
|
||||
#endif
|
||||
|
||||
#ifdef PALMOS_MODE
|
||||
extern void PalmFatalError(const char *err);
|
||||
PalmFatalError(buf_output);
|
||||
|
|
43
configure
vendored
43
configure
vendored
|
@ -999,6 +999,11 @@ wince)
|
|||
_host_cpu=arm
|
||||
_host_alias=arm-wince-mingw32ce
|
||||
;;
|
||||
android)
|
||||
_host_os=android
|
||||
_host_cpu=arm
|
||||
_host_alias=arm-android-eabi
|
||||
;;
|
||||
*)
|
||||
if test -n "$_host"; then
|
||||
guessed_host=`$_srcdir/config.sub $_host`
|
||||
|
@ -1077,6 +1082,12 @@ psp)
|
|||
exit 1
|
||||
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
|
||||
|
@ -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 -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
|
||||
*)
|
||||
echo "WARNING: could not establish system type, assuming unix like"
|
||||
|
@ -1647,6 +1663,19 @@ if test -n "$_host"; then
|
|||
_mt32emu="no"
|
||||
_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"
|
||||
;;
|
||||
|
@ -1825,7 +1854,7 @@ POST_OBJS_FLAGS := -Wl,-no-whole-archive
|
|||
LIBS += -ldl
|
||||
'
|
||||
;;
|
||||
linux*)
|
||||
linux*|android)
|
||||
_def_plugin='
|
||||
#define PLUGIN_PREFIX "lib"
|
||||
#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'
|
||||
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"
|
||||
exit 1
|
||||
|
@ -2447,7 +2484,7 @@ if test "$have_gcc" = yes ; then
|
|||
case $_host_os in
|
||||
# newlib-based system include files suppress non-C89 function
|
||||
# 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"
|
||||
;;
|
||||
*)
|
||||
|
@ -2468,7 +2505,7 @@ fi;
|
|||
|
||||
# Some platforms use certain GNU extensions in header files
|
||||
case $_host_os in
|
||||
gamecube | psp | wii)
|
||||
gamecube | psp | wii | android)
|
||||
;;
|
||||
*)
|
||||
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"
|
||||
|
||||
#ifdef USE_TREMOR
|
||||
#ifdef __GP32__ // GP32 uses custom libtremor
|
||||
#if defined(ANDROID) || defined(__GP32__) // custom libtremor locations
|
||||
#include <ivorbisfile.h>
|
||||
#else
|
||||
#include <tremor/ivorbisfile.h>
|
||||
|
|
|
@ -40,6 +40,7 @@ my @subs_files = qw(
|
|||
dists/iphone/Info.plist
|
||||
dists/irix/scummvm.spec
|
||||
dists/wii/meta.xml
|
||||
dists/android/AndroidManifest.xml
|
||||
backends/platform/psp/README.PSP
|
||||
);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue