From 44b7f3aed52b1213995f9e19e4395f4350997b01 Mon Sep 17 00:00:00 2001 From: Alyssa Milburn Date: Tue, 25 Oct 2011 21:18:42 +0200 Subject: ANDROID: Move from org.inodes.gus to org.scummvm. --- backends/platform/android/android.mk | 6 +- backends/platform/android/jni.cpp | 2 +- .../inodes/gus/scummvm/EditableSurfaceView.java | 61 --- .../org/inodes/gus/scummvm/PluginProvider.java | 53 --- .../android/org/inodes/gus/scummvm/ScummVM.java | 451 --------------------- .../org/inodes/gus/scummvm/ScummVMActivity.java | 224 ---------- .../org/inodes/gus/scummvm/ScummVMApplication.java | 30 -- .../org/inodes/gus/scummvm/ScummVMEvents.java | 231 ----------- .../android/org/inodes/gus/scummvm/Unpacker.java | 378 ----------------- .../org/scummvm/scummvm/EditableSurfaceView.java | 61 +++ .../org/scummvm/scummvm/PluginProvider.java | 53 +++ .../android/org/scummvm/scummvm/ScummVM.java | 451 +++++++++++++++++++++ .../org/scummvm/scummvm/ScummVMActivity.java | 224 ++++++++++ .../org/scummvm/scummvm/ScummVMApplication.java | 30 ++ .../android/org/scummvm/scummvm/ScummVMEvents.java | 231 +++++++++++ .../android/org/scummvm/scummvm/Unpacker.java | 378 +++++++++++++++++ dists/android/AndroidManifest.xml | 10 +- dists/android/AndroidManifest.xml.in | 10 +- dists/android/plugin-manifest.xml | 14 +- dists/android/plugin-manifest.xml.in | 14 +- dists/android/res/layout/main.xml | 2 +- 21 files changed, 1457 insertions(+), 1457 deletions(-) delete mode 100644 backends/platform/android/org/inodes/gus/scummvm/EditableSurfaceView.java delete mode 100644 backends/platform/android/org/inodes/gus/scummvm/PluginProvider.java delete mode 100644 backends/platform/android/org/inodes/gus/scummvm/ScummVM.java delete mode 100644 backends/platform/android/org/inodes/gus/scummvm/ScummVMActivity.java delete mode 100644 backends/platform/android/org/inodes/gus/scummvm/ScummVMApplication.java delete mode 100644 backends/platform/android/org/inodes/gus/scummvm/ScummVMEvents.java delete mode 100644 backends/platform/android/org/inodes/gus/scummvm/Unpacker.java create mode 100644 backends/platform/android/org/scummvm/scummvm/EditableSurfaceView.java create mode 100644 backends/platform/android/org/scummvm/scummvm/PluginProvider.java create mode 100644 backends/platform/android/org/scummvm/scummvm/ScummVM.java create mode 100644 backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java create mode 100644 backends/platform/android/org/scummvm/scummvm/ScummVMApplication.java create mode 100644 backends/platform/android/org/scummvm/scummvm/ScummVMEvents.java create mode 100644 backends/platform/android/org/scummvm/scummvm/Unpacker.java diff --git a/backends/platform/android/android.mk b/backends/platform/android/android.mk index 63d194fdc1..2e8fd62152 100644 --- a/backends/platform/android/android.mk +++ b/backends/platform/android/android.mk @@ -63,7 +63,7 @@ PATH_BUILD_CLASSES_PLUGIN_TOP = $(PATH_BUILD)/classes.plugin PATH_STAGE_PREFIX = build.stage PATH_STAGE_MAIN = $(PATH_STAGE_PREFIX).main -PATH_REL = org/inodes/gus/scummvm +PATH_REL = org/scummvm/scummvm PATH_SRC_TOP = $(srcdir)/backends/platform/android PATH_SRC = $(PATH_SRC_TOP)/$(PATH_REL) @@ -172,13 +172,13 @@ androidrelease: $(addprefix release/, $(APK_MAIN) $(APK_PLUGINS)) androidtestmain: $(APK_MAIN) $(ADB) install -r $(APK_MAIN) - $(ADB) shell am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -n org.inodes.gus.scummvm/.Unpacker + $(ADB) shell am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -n org.scummvm.scummvm/.Unpacker androidtest: $(APK_MAIN) $(APK_PLUGINS) @set -e; for apk in $^; do \ $(ADB) install -r $$apk; \ done - $(ADB) shell am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -n org.inodes.gus.scummvm/.Unpacker + $(ADB) shell am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -n org.scummvm.scummvm/.Unpacker # used by buildbot! androiddistdebug: all diff --git a/backends/platform/android/jni.cpp b/backends/platform/android/jni.cpp index b44a585528..a7ebb87651 100644 --- a/backends/platform/android/jni.cpp +++ b/backends/platform/android/jni.cpp @@ -125,7 +125,7 @@ jint JNI::onLoad(JavaVM *vm) { if (_vm->GetEnv((void **)&env, JNI_VERSION_1_2)) return JNI_ERR; - jclass cls = env->FindClass("org/inodes/gus/scummvm/ScummVM"); + jclass cls = env->FindClass("org/scummvm/scummvm/ScummVM"); if (cls == 0) return JNI_ERR; diff --git a/backends/platform/android/org/inodes/gus/scummvm/EditableSurfaceView.java b/backends/platform/android/org/inodes/gus/scummvm/EditableSurfaceView.java deleted file mode 100644 index 3aef14b851..0000000000 --- a/backends/platform/android/org/inodes/gus/scummvm/EditableSurfaceView.java +++ /dev/null @@ -1,61 +0,0 @@ -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 false; - } - - private class MyInputConnection extends BaseInputConnection { - public MyInputConnection() { - super(EditableSurfaceView.this, false); - } - - @Override - public boolean performEditorAction(int actionCode) { - if (actionCode == EditorInfo.IME_ACTION_DONE) { - InputMethodManager imm = (InputMethodManager) - getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(getWindowToken(), 0); - } - - // Sends enter key - return super.performEditorAction(actionCode); - } - } - - @Override - public InputConnection onCreateInputConnection(EditorInfo outAttrs) { - outAttrs.initialCapsMode = 0; - outAttrs.initialSelEnd = outAttrs.initialSelStart = -1; - outAttrs.inputType = (InputType.TYPE_CLASS_TEXT | - InputType.TYPE_TEXT_VARIATION_NORMAL | - InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE); - outAttrs.imeOptions = (EditorInfo.IME_ACTION_DONE | - EditorInfo.IME_FLAG_NO_EXTRACT_UI); - - return new MyInputConnection(); - } -} diff --git a/backends/platform/android/org/inodes/gus/scummvm/PluginProvider.java b/backends/platform/android/org/inodes/gus/scummvm/PluginProvider.java deleted file mode 100644 index d90b7b2c68..0000000000 --- a/backends/platform/android/org/inodes/gus/scummvm/PluginProvider.java +++ /dev/null @@ -1,53 +0,0 @@ -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 { - private final static String LOG_TAG = "ScummVM"; - - 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(LOG_TAG, "Error finding my own info?", e); - return; - } - - String mylib = info.metaData.getString(META_UNPACK_LIB); - if (mylib != null) { - ArrayList 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); - } -} diff --git a/backends/platform/android/org/inodes/gus/scummvm/ScummVM.java b/backends/platform/android/org/inodes/gus/scummvm/ScummVM.java deleted file mode 100644 index 246a02c9be..0000000000 --- a/backends/platform/android/org/inodes/gus/scummvm/ScummVM.java +++ /dev/null @@ -1,451 +0,0 @@ -package org.inodes.gus.scummvm; - -import android.util.Log; -import android.content.res.AssetManager; -import android.view.SurfaceHolder; -import android.media.AudioFormat; -import android.media.AudioManager; -import android.media.AudioTrack; - -import javax.microedition.khronos.opengles.GL10; -import javax.microedition.khronos.egl.EGL10; -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.egl.EGLContext; -import javax.microedition.khronos.egl.EGLDisplay; -import javax.microedition.khronos.egl.EGLSurface; - -import java.io.File; -import java.util.LinkedHashMap; - -public abstract class ScummVM implements SurfaceHolder.Callback, Runnable { - final protected static String LOG_TAG = "ScummVM"; - final private AssetManager _asset_manager; - final private Object _sem_surface; - - private EGL10 _egl; - private EGLDisplay _egl_display = EGL10.EGL_NO_DISPLAY; - private EGLConfig _egl_config; - private EGLContext _egl_context = EGL10.EGL_NO_CONTEXT; - private EGLSurface _egl_surface = EGL10.EGL_NO_SURFACE; - - private SurfaceHolder _surface_holder; - private AudioTrack _audio_track; - private int _sample_rate = 0; - private int _buffer_size = 0; - - private String[] _args; - - final private native void create(AssetManager asset_manager, - EGL10 egl, EGLDisplay egl_display, - AudioTrack audio_track, - int sample_rate, int buffer_size); - final private native void destroy(); - final private native void setSurface(int width, int height); - final private native int main(String[] args); - - // pause the engine and all native threads - final public native void setPause(boolean pause); - final public native void enableZoning(boolean enable); - // Feed an event to ScummVM. Safe to call from other threads. - final public native void pushEvent(int type, int arg1, int arg2, int arg3, - int arg4, int arg5); - - // Callbacks from C++ peer instance - abstract protected void getDPI(float[] values); - abstract protected void displayMessageOnOSD(String msg); - abstract protected void setWindowCaption(String caption); - abstract protected String[] getPluginDirectories(); - abstract protected void showVirtualKeyboard(boolean enable); - abstract protected String[] getSysArchives(); - - public ScummVM(AssetManager asset_manager, SurfaceHolder holder) { - _asset_manager = asset_manager; - _sem_surface = new Object(); - - holder.addCallback(this); - } - - // SurfaceHolder callback - final public void surfaceCreated(SurfaceHolder holder) { - Log.d(LOG_TAG, "surfaceCreated"); - - // no need to do anything, surfaceChanged() will be called in any case - } - - // SurfaceHolder callback - final public void surfaceChanged(SurfaceHolder holder, int format, - int width, int height) { - // the orientation may reset on standby mode and the theme manager - // could assert when using a portrait resolution. so lets not do that. - if (height > width) { - Log.d(LOG_TAG, String.format("Ignoring surfaceChanged: %dx%d (%d)", - width, height, format)); - return; - } - - Log.d(LOG_TAG, String.format("surfaceChanged: %dx%d (%d)", - width, height, format)); - - synchronized(_sem_surface) { - _surface_holder = holder; - _sem_surface.notifyAll(); - } - - // store values for the native code - setSurface(width, height); - } - - // SurfaceHolder callback - final public void surfaceDestroyed(SurfaceHolder holder) { - Log.d(LOG_TAG, "surfaceDestroyed"); - - synchronized(_sem_surface) { - _surface_holder = null; - _sem_surface.notifyAll(); - } - - // clear values for the native code - setSurface(0, 0); - } - - final public void setArgs(String[] args) { - _args = args; - } - - final public void run() { - try { - initAudio(); - initEGL(); - - // wait for the surfaceChanged callback - synchronized(_sem_surface) { - while (_surface_holder == null) - _sem_surface.wait(); - } - } catch (Exception e) { - deinitEGL(); - deinitAudio(); - - throw new RuntimeException("Error preparing the ScummVM thread", e); - } - - create(_asset_manager, _egl, _egl_display, - _audio_track, _sample_rate, _buffer_size); - - int res = main(_args); - - destroy(); - - deinitEGL(); - deinitAudio(); - - // On exit, tear everything down for a fresh restart next time. - System.exit(res); - } - - final private void initEGL() throws Exception { - _egl = (EGL10)EGLContext.getEGL(); - _egl_display = _egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); - - int[] version = new int[2]; - _egl.eglInitialize(_egl_display, version); - - int[] num_config = new int[1]; - _egl.eglGetConfigs(_egl_display, null, 0, num_config); - - final int numConfigs = num_config[0]; - - if (numConfigs <= 0) - throw new IllegalArgumentException("No EGL configs"); - - EGLConfig[] configs = new EGLConfig[numConfigs]; - _egl.eglGetConfigs(_egl_display, configs, numConfigs, num_config); - - // Android's eglChooseConfig is busted in several versions and - // devices so we have to filter/rank the configs ourselves. - _egl_config = chooseEglConfig(configs); - - _egl_context = _egl.eglCreateContext(_egl_display, _egl_config, - EGL10.EGL_NO_CONTEXT, null); - - if (_egl_context == EGL10.EGL_NO_CONTEXT) - throw new Exception(String.format("Failed to create context: 0x%x", - _egl.eglGetError())); - } - - // Callback from C++ peer instance - final protected EGLSurface initSurface() throws Exception { - _egl_surface = _egl.eglCreateWindowSurface(_egl_display, _egl_config, - _surface_holder, null); - - if (_egl_surface == EGL10.EGL_NO_SURFACE) - throw new Exception(String.format( - "eglCreateWindowSurface failed: 0x%x", _egl.eglGetError())); - - _egl.eglMakeCurrent(_egl_display, _egl_surface, _egl_surface, - _egl_context); - - GL10 gl = (GL10)_egl_context.getGL(); - - Log.i(LOG_TAG, String.format("Using EGL %s (%s); GL %s/%s (%s)", - _egl.eglQueryString(_egl_display, EGL10.EGL_VERSION), - _egl.eglQueryString(_egl_display, EGL10.EGL_VENDOR), - gl.glGetString(GL10.GL_VERSION), - gl.glGetString(GL10.GL_RENDERER), - gl.glGetString(GL10.GL_VENDOR))); - - return _egl_surface; - } - - // Callback from C++ peer instance - final protected void deinitSurface() { - if (_egl_display != EGL10.EGL_NO_DISPLAY) { - _egl.eglMakeCurrent(_egl_display, EGL10.EGL_NO_SURFACE, - EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); - - if (_egl_surface != EGL10.EGL_NO_SURFACE) - _egl.eglDestroySurface(_egl_display, _egl_surface); - } - - _egl_surface = EGL10.EGL_NO_SURFACE; - } - - final private void deinitEGL() { - if (_egl_display != EGL10.EGL_NO_DISPLAY) { - _egl.eglMakeCurrent(_egl_display, EGL10.EGL_NO_SURFACE, - EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); - - if (_egl_surface != EGL10.EGL_NO_SURFACE) - _egl.eglDestroySurface(_egl_display, _egl_surface); - - if (_egl_context != EGL10.EGL_NO_CONTEXT) - _egl.eglDestroyContext(_egl_display, _egl_context); - - _egl.eglTerminate(_egl_display); - } - - _egl_surface = EGL10.EGL_NO_SURFACE; - _egl_context = EGL10.EGL_NO_CONTEXT; - _egl_config = null; - _egl_display = EGL10.EGL_NO_DISPLAY; - _egl = null; - } - - final private void initAudio() throws Exception { - _sample_rate = AudioTrack.getNativeOutputSampleRate( - AudioManager.STREAM_MUSIC); - _buffer_size = AudioTrack.getMinBufferSize(_sample_rate, - AudioFormat.CHANNEL_CONFIGURATION_STEREO, - AudioFormat.ENCODING_PCM_16BIT); - - // ~50ms - int buffer_size_want = (_sample_rate * 2 * 2 / 20) & ~1023; - - if (_buffer_size < buffer_size_want) { - Log.w(LOG_TAG, String.format( - "adjusting audio buffer size (was: %d)", _buffer_size)); - - _buffer_size = buffer_size_want; - } - - Log.i(LOG_TAG, String.format("Using %d bytes buffer for %dHz audio", - _buffer_size, _sample_rate)); - - _audio_track = new AudioTrack(AudioManager.STREAM_MUSIC, - _sample_rate, - AudioFormat.CHANNEL_CONFIGURATION_STEREO, - AudioFormat.ENCODING_PCM_16BIT, - _buffer_size, - AudioTrack.MODE_STREAM); - - if (_audio_track.getState() != AudioTrack.STATE_INITIALIZED) - throw new Exception( - String.format("Error initializing AudioTrack: %d", - _audio_track.getState())); - } - - final private void deinitAudio() { - if (_audio_track != null) - _audio_track.stop(); - - _audio_track = null; - _buffer_size = 0; - _sample_rate = 0; - } - - private static final int[] s_eglAttribs = { - EGL10.EGL_CONFIG_ID, - EGL10.EGL_BUFFER_SIZE, - EGL10.EGL_RED_SIZE, - EGL10.EGL_GREEN_SIZE, - EGL10.EGL_BLUE_SIZE, - EGL10.EGL_ALPHA_SIZE, - EGL10.EGL_CONFIG_CAVEAT, - EGL10.EGL_DEPTH_SIZE, - EGL10.EGL_LEVEL, - EGL10.EGL_MAX_PBUFFER_WIDTH, - EGL10.EGL_MAX_PBUFFER_HEIGHT, - EGL10.EGL_MAX_PBUFFER_PIXELS, - EGL10.EGL_NATIVE_RENDERABLE, - EGL10.EGL_NATIVE_VISUAL_ID, - EGL10.EGL_NATIVE_VISUAL_TYPE, - EGL10.EGL_SAMPLE_BUFFERS, - EGL10.EGL_SAMPLES, - EGL10.EGL_STENCIL_SIZE, - EGL10.EGL_SURFACE_TYPE, - EGL10.EGL_TRANSPARENT_TYPE, - EGL10.EGL_TRANSPARENT_RED_VALUE, - EGL10.EGL_TRANSPARENT_GREEN_VALUE, - EGL10.EGL_TRANSPARENT_BLUE_VALUE - }; - - final private class EglAttribs extends LinkedHashMap { - public EglAttribs(EGLConfig config) { - super(s_eglAttribs.length); - - int[] value = new int[1]; - - for (int i : s_eglAttribs) { - _egl.eglGetConfigAttrib(_egl_display, config, i, value); - - put(i, value[0]); - } - } - - private int weightBits(int attr, int size) { - final int value = get(attr); - - int score = 0; - - if (value == size || (size > 0 && value > size)) - score += 10; - - // penalize for wasted bits - score -= value - size; - - return score; - } - - public int weight() { - int score = 10000; - - if (get(EGL10.EGL_CONFIG_CAVEAT) != EGL10.EGL_NONE) - score -= 1000; - - // less MSAA is better - score -= get(EGL10.EGL_SAMPLES) * 100; - - // Must be at least 565, but then smaller is better - score += weightBits(EGL10.EGL_RED_SIZE, 5); - score += weightBits(EGL10.EGL_GREEN_SIZE, 6); - score += weightBits(EGL10.EGL_BLUE_SIZE, 5); - score += weightBits(EGL10.EGL_ALPHA_SIZE, 0); - score += weightBits(EGL10.EGL_DEPTH_SIZE, 0); - score += weightBits(EGL10.EGL_STENCIL_SIZE, 0); - - return score; - } - - public String toString() { - String s; - - if (get(EGL10.EGL_ALPHA_SIZE) > 0) - s = String.format("[%d] RGBA%d%d%d%d", - get(EGL10.EGL_CONFIG_ID), - get(EGL10.EGL_RED_SIZE), - get(EGL10.EGL_GREEN_SIZE), - get(EGL10.EGL_BLUE_SIZE), - get(EGL10.EGL_ALPHA_SIZE)); - else - s = String.format("[%d] RGB%d%d%d", - get(EGL10.EGL_CONFIG_ID), - get(EGL10.EGL_RED_SIZE), - get(EGL10.EGL_GREEN_SIZE), - get(EGL10.EGL_BLUE_SIZE)); - - if (get(EGL10.EGL_DEPTH_SIZE) > 0) - s += String.format(" D%d", get(EGL10.EGL_DEPTH_SIZE)); - - if (get(EGL10.EGL_STENCIL_SIZE) > 0) - s += String.format(" S%d", get(EGL10.EGL_STENCIL_SIZE)); - - if (get(EGL10.EGL_SAMPLES) > 0) - s += String.format(" MSAAx%d", get(EGL10.EGL_SAMPLES)); - - if ((get(EGL10.EGL_SURFACE_TYPE) & EGL10.EGL_WINDOW_BIT) > 0) - s += " W"; - if ((get(EGL10.EGL_SURFACE_TYPE) & EGL10.EGL_PBUFFER_BIT) > 0) - s += " P"; - if ((get(EGL10.EGL_SURFACE_TYPE) & EGL10.EGL_PIXMAP_BIT) > 0) - s += " X"; - - switch (get(EGL10.EGL_CONFIG_CAVEAT)) { - case EGL10.EGL_NONE: - break; - - case EGL10.EGL_SLOW_CONFIG: - s += " SLOW"; - break; - - case EGL10.EGL_NON_CONFORMANT_CONFIG: - s += " NON_CONFORMANT"; - - default: - s += String.format(" unknown CAVEAT 0x%x", - get(EGL10.EGL_CONFIG_CAVEAT)); - } - - return s; - } - }; - - final private EGLConfig chooseEglConfig(EGLConfig[] configs) { - EGLConfig res = configs[0]; - int bestScore = -1; - - Log.d(LOG_TAG, "EGL configs:"); - - for (EGLConfig config : configs) { - EglAttribs attr = new EglAttribs(config); - - // must have - if ((attr.get(EGL10.EGL_SURFACE_TYPE) & EGL10.EGL_WINDOW_BIT) == 0) - continue; - - int score = attr.weight(); - - Log.d(LOG_TAG, String.format("%s (%d)", attr.toString(), score)); - - if (score > bestScore) { - res = config; - bestScore = score; - } - } - - if (bestScore < 0) - Log.e(LOG_TAG, - "Unable to find an acceptable EGL config, expect badness."); - - Log.d(LOG_TAG, String.format("Chosen EGL config: %s", - new EglAttribs(res).toString())); - - return res; - } - - static { - // For grabbing with gdb... - final boolean sleep_for_debugger = false; - if (sleep_for_debugger) { - try { - Thread.sleep(20 * 1000); - } catch (InterruptedException e) { - } - } - - File cache_dir = ScummVMApplication.getLastCacheDir(); - String libname = System.mapLibraryName("scummvm"); - File libpath = new File(cache_dir, libname); - - System.load(libpath.getPath()); - } -} diff --git a/backends/platform/android/org/inodes/gus/scummvm/ScummVMActivity.java b/backends/platform/android/org/inodes/gus/scummvm/ScummVMActivity.java deleted file mode 100644 index ce4e016322..0000000000 --- a/backends/platform/android/org/inodes/gus/scummvm/ScummVMActivity.java +++ /dev/null @@ -1,224 +0,0 @@ -package org.inodes.gus.scummvm; - -import android.app.Activity; -import android.app.AlertDialog; -import android.content.DialogInterface; -import android.media.AudioManager; -import android.os.Bundle; -import android.os.Environment; -import android.util.DisplayMetrics; -import android.util.Log; -import android.view.SurfaceView; -import android.view.SurfaceHolder; -import android.view.MotionEvent; -import android.view.inputmethod.InputMethodManager; -import android.widget.Toast; - -public class ScummVMActivity extends Activity { - - private class MyScummVM extends ScummVM { - private boolean usingSmallScreen() { - // Multiple screen sizes came in with Android 1.6. Have - // to use reflection in order to continue supporting 1.5 - // devices :( - DisplayMetrics metrics = new DisplayMetrics(); - getWindowManager().getDefaultDisplay().getMetrics(metrics); - - try { - // This 'density' term is very confusing. - int DENSITY_LOW = metrics.getClass().getField("DENSITY_LOW").getInt(null); - int densityDpi = metrics.getClass().getField("densityDpi").getInt(metrics); - return densityDpi <= DENSITY_LOW; - } catch (Exception e) { - return false; - } - } - - public MyScummVM(SurfaceHolder holder) { - super(ScummVMActivity.this.getAssets(), holder); - - // Enable ScummVM zoning on 'small' screens. - // FIXME make this optional for the user - // disabled for now since it crops too much - //enableZoning(usingSmallScreen()); - } - - @Override - protected void getDPI(float[] values) { - DisplayMetrics metrics = new DisplayMetrics(); - getWindowManager().getDefaultDisplay().getMetrics(metrics); - - values[0] = metrics.xdpi; - values[1] = metrics.ydpi; - } - - @Override - protected void displayMessageOnOSD(String msg) { - Log.i(LOG_TAG, "OSD: " + msg); - Toast.makeText(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) { - runOnUiThread(new Runnable() { - public void run() { - showKeyboard(enable); - } - }); - } - - @Override - protected String[] getSysArchives() { - return new String[0]; - } - - } - - private MyScummVM _scummvm; - private ScummVMEvents _events; - private Thread _scummvm_thread; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setVolumeControlStream(AudioManager.STREAM_MUSIC); - - setContentView(R.layout.main); - takeKeyEvents(true); - - // This is a common enough error that we should warn about it - // explicitly. - if (!Environment.getExternalStorageDirectory().canRead()) { - new AlertDialog.Builder(this) - .setTitle(R.string.no_sdcard_title) - .setIcon(android.R.drawable.ic_dialog_alert) - .setMessage(R.string.no_sdcard) - .setNegativeButton(R.string.quit, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, - int which) { - finish(); - } - }) - .show(); - - return; - } - - SurfaceView main_surface = (SurfaceView)findViewById(R.id.main_surface); - - main_surface.requestFocus(); - - getFilesDir().mkdirs(); - - // Start ScummVM - _scummvm = new MyScummVM(main_surface.getHolder()); - - _scummvm.setArgs(new String[] { - "ScummVM", - "--config=" + getFileStreamPath("scummvmrc").getPath(), - "--path=" + Environment.getExternalStorageDirectory().getPath(), - "--gui-theme=scummmodern", - "--savepath=" + getDir("saves", 0).getPath() - }); - - _events = new ScummVMEvents(this, _scummvm); - - main_surface.setOnKeyListener(_events); - main_surface.setOnTouchListener(_events); - - _scummvm_thread = new Thread(_scummvm, "ScummVM"); - _scummvm_thread.start(); - } - - @Override - public void onStart() { - Log.d(ScummVM.LOG_TAG, "onStart"); - - super.onStart(); - } - - @Override - public void onResume() { - Log.d(ScummVM.LOG_TAG, "onResume"); - - super.onResume(); - - if (_scummvm != null) - _scummvm.setPause(false); - } - - @Override - public void onPause() { - Log.d(ScummVM.LOG_TAG, "onPause"); - - super.onPause(); - - if (_scummvm != null) - _scummvm.setPause(true); - } - - @Override - public void onStop() { - Log.d(ScummVM.LOG_TAG, "onStop"); - - super.onStop(); - } - - @Override - public void onDestroy() { - Log.d(ScummVM.LOG_TAG, "onDestroy"); - - super.onDestroy(); - - if (_events != null) { - _events.sendQuitEvent(); - - try { - // 1s timeout - _scummvm_thread.join(1000); - } catch (InterruptedException e) { - Log.i(ScummVM.LOG_TAG, "Error while joining ScummVM thread", e); - } - - _scummvm = null; - } - } - - @Override - public boolean onTrackballEvent(MotionEvent e) { - if (_events != null) - return _events.onTrackballEvent(e); - - return false; - } - - private void showKeyboard(boolean show) { - SurfaceView main_surface = (SurfaceView)findViewById(R.id.main_surface); - InputMethodManager imm = (InputMethodManager) - getSystemService(INPUT_METHOD_SERVICE); - - if (show) - imm.showSoftInput(main_surface, InputMethodManager.SHOW_IMPLICIT); - else - imm.hideSoftInputFromWindow(main_surface.getWindowToken(), - InputMethodManager.HIDE_IMPLICIT_ONLY); - } -} diff --git a/backends/platform/android/org/inodes/gus/scummvm/ScummVMApplication.java b/backends/platform/android/org/inodes/gus/scummvm/ScummVMApplication.java deleted file mode 100644 index 8ab7d1a084..0000000000 --- a/backends/platform/android/org/inodes/gus/scummvm/ScummVMApplication.java +++ /dev/null @@ -1,30 +0,0 @@ -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; - } -} diff --git a/backends/platform/android/org/inodes/gus/scummvm/ScummVMEvents.java b/backends/platform/android/org/inodes/gus/scummvm/ScummVMEvents.java deleted file mode 100644 index 175ff0b677..0000000000 --- a/backends/platform/android/org/inodes/gus/scummvm/ScummVMEvents.java +++ /dev/null @@ -1,231 +0,0 @@ -package org.inodes.gus.scummvm; - -import android.os.Handler; -import android.os.Message; -import android.util.Log; -import android.content.Context; -import android.view.KeyEvent; -import android.view.KeyCharacterMap; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.GestureDetector; -import android.view.inputmethod.InputMethodManager; - -public class ScummVMEvents implements - android.view.View.OnKeyListener, - android.view.View.OnTouchListener, - android.view.GestureDetector.OnGestureListener, - android.view.GestureDetector.OnDoubleTapListener { - - public static final int JE_SYS_KEY = 0; - public static final int JE_KEY = 1; - public static final int JE_DPAD = 2; - public static final int JE_DOWN = 3; - public static final int JE_SCROLL = 4; - public static final int JE_TAP = 5; - public static final int JE_DOUBLE_TAP = 6; - public static final int JE_MULTI = 7; - public static final int JE_BALL = 8; - public static final int JE_QUIT = 0x1000; - - final protected Context _context; - final protected ScummVM _scummvm; - final protected GestureDetector _gd; - final protected int _longPress; - - public ScummVMEvents(Context context, ScummVM scummvm) { - _context = context; - _scummvm = scummvm; - - _gd = new GestureDetector(context, this); - _gd.setOnDoubleTapListener(this); - _gd.setIsLongpressEnabled(false); - - _longPress = ViewConfiguration.getLongPressTimeout(); - } - - final public void sendQuitEvent() { - _scummvm.pushEvent(JE_QUIT, 0, 0, 0, 0, 0); - } - - public boolean onTrackballEvent(MotionEvent e) { - _scummvm.pushEvent(JE_BALL, e.getAction(), - (int)(e.getX() * e.getXPrecision() * 100), - (int)(e.getY() * e.getYPrecision() * 100), - 0, 0); - return true; - } - - final static int MSG_MENU_LONG_PRESS = 1; - - final private Handler keyHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - if (msg.what == MSG_MENU_LONG_PRESS) { - InputMethodManager imm = (InputMethodManager) - _context.getSystemService(_context.INPUT_METHOD_SERVICE); - - if (imm != null) - imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); - } - } - }; - - // OnKeyListener - final public boolean onKey(View v, int keyCode, KeyEvent e) { - final int action = e.getAction(); - - if (e.isSystem()) { - // filter what we handle - switch (keyCode) { - case KeyEvent.KEYCODE_BACK: - case KeyEvent.KEYCODE_MENU: - case KeyEvent.KEYCODE_CAMERA: - case KeyEvent.KEYCODE_SEARCH: - break; - - default: - return false; - } - - // no repeats for system keys - if (e.getRepeatCount() > 0) - return false; - - // Have to reimplement hold-down-menu-brings-up-softkeybd - // ourselves, since we are otherwise hijacking the menu key :( - // See com.android.internal.policy.impl.PhoneWindow.onKeyDownPanel() - // for the usual Android implementation of this feature. - if (keyCode == KeyEvent.KEYCODE_MENU) { - final boolean fired = - !keyHandler.hasMessages(MSG_MENU_LONG_PRESS); - - keyHandler.removeMessages(MSG_MENU_LONG_PRESS); - - if (action == KeyEvent.ACTION_DOWN) { - keyHandler.sendMessageDelayed(keyHandler.obtainMessage( - MSG_MENU_LONG_PRESS), _longPress); - return true; - } - - if (fired) - return true; - - // only send up events of the menu button to the native side - if (action != KeyEvent.ACTION_UP) - return true; - } - - _scummvm.pushEvent(JE_SYS_KEY, action, keyCode, 0, 0, 0); - - return true; - } - - // sequence of characters - if (action == KeyEvent.ACTION_MULTIPLE && - keyCode == KeyEvent.KEYCODE_UNKNOWN) { - final KeyCharacterMap m = KeyCharacterMap.load(e.getDeviceId()); - final KeyEvent[] es = m.getEvents(e.getCharacters().toCharArray()); - - if (es == null) - return true; - - for (KeyEvent s : es) { - _scummvm.pushEvent(JE_KEY, s.getAction(), s.getKeyCode(), - s.getUnicodeChar() & KeyCharacterMap.COMBINING_ACCENT_MASK, - s.getMetaState(), s.getRepeatCount()); - } - - return true; - } - - switch (keyCode) { - case KeyEvent.KEYCODE_DPAD_UP: - case KeyEvent.KEYCODE_DPAD_DOWN: - case KeyEvent.KEYCODE_DPAD_LEFT: - case KeyEvent.KEYCODE_DPAD_RIGHT: - case KeyEvent.KEYCODE_DPAD_CENTER: - _scummvm.pushEvent(JE_DPAD, action, keyCode, - (int)(e.getEventTime() - e.getDownTime()), - e.getRepeatCount(), 0); - return true; - } - - _scummvm.pushEvent(JE_KEY, action, keyCode, - e.getUnicodeChar() & KeyCharacterMap.COMBINING_ACCENT_MASK, - e.getMetaState(), e.getRepeatCount()); - - return true; - } - - // OnTouchListener - final public boolean onTouch(View v, MotionEvent e) { - final int action = e.getAction(); - - // constants from APIv5: - // (action & ACTION_POINTER_INDEX_MASK) >> ACTION_POINTER_INDEX_SHIFT - final int pointer = (action & 0xff00) >> 8; - - if (pointer > 0) { - _scummvm.pushEvent(JE_MULTI, pointer, action & 0xff, // ACTION_MASK - (int)e.getX(), (int)e.getY(), 0); - return true; - } - - return _gd.onTouchEvent(e); - } - - // OnGestureListener - final public boolean onDown(MotionEvent e) { - _scummvm.pushEvent(JE_DOWN, (int)e.getX(), (int)e.getY(), 0, 0, 0); - return true; - } - - final public boolean onFling(MotionEvent e1, MotionEvent e2, - float velocityX, float velocityY) { - //Log.d(ScummVM.LOG_TAG, String.format("onFling: %s -> %s (%.3f %.3f)", - // e1.toString(), e2.toString(), - // velocityX, velocityY)); - - return true; - } - - final public void onLongPress(MotionEvent e) { - // disabled, interferes with drag&drop - } - - final public boolean onScroll(MotionEvent e1, MotionEvent e2, - float distanceX, float distanceY) { - _scummvm.pushEvent(JE_SCROLL, (int)e1.getX(), (int)e1.getY(), - (int)e2.getX(), (int)e2.getY(), 0); - - return true; - } - - final public void onShowPress(MotionEvent e) { - } - - final public boolean onSingleTapUp(MotionEvent e) { - _scummvm.pushEvent(JE_TAP, (int)e.getX(), (int)e.getY(), - (int)(e.getEventTime() - e.getDownTime()), 0, 0); - - return true; - } - - // OnDoubleTapListener - final public boolean onDoubleTap(MotionEvent e) { - return true; - } - - final public boolean onDoubleTapEvent(MotionEvent e) { - _scummvm.pushEvent(JE_DOUBLE_TAP, (int)e.getX(), (int)e.getY(), - e.getAction(), 0, 0); - - return true; - } - - final public boolean onSingleTapConfirmed(MotionEvent e) { - return true; - } -} diff --git a/backends/platform/android/org/inodes/gus/scummvm/Unpacker.java b/backends/platform/android/org/inodes/gus/scummvm/Unpacker.java deleted file mode 100644 index 6cc7f8eadb..0000000000 --- a/backends/platform/android/org/inodes/gus/scummvm/Unpacker.java +++ /dev/null @@ -1,378 +0,0 @@ -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 { - protected final static String LOG_TAG = "ScummVM"; - // TODO don't hardcode this - private final static boolean PLUGINS_ENABLED = false; - private final static String META_NEXT_ACTIVITY = - "org.inodes.gus.unpacker.nextActivity"; - private ProgressBar mProgress; - private File mUnpackDest; // location to unpack into - private AsyncTask mUnpacker; - private final static int REQUEST_MARKET = 1; - - // Android 3.1+ only - public static final int FLAG_INCLUDE_STOPPED_PACKAGES = 32; - - private static class UnpackJob { - public ZipFile zipfile; - public Set paths; - - public UnpackJob(ZipFile zipfile, Set 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 { - @Override - protected void onProgressUpdate(Integer... progress) { - mProgress.setIndeterminate(false); - mProgress.setMax(progress[1]); - mProgress.setProgress(progress[0]); - mProgress.postInvalidate(); - } - - @Override - protected void onPostExecute(Void result) { - Bundle md = getMetaData(); - String nextActivity = md.getString(META_NEXT_ACTIVITY); - if (nextActivity != null) { - final ComponentName cn = - ComponentName.unflattenFromString(nextActivity); - if (cn != null) { - final Intent origIntent = getIntent(); - Intent intent = new Intent(); - intent.setComponent(cn); - if (origIntent.getExtras() != null) - intent.putExtras(origIntent.getExtras()); - intent.putExtra(Intent.EXTRA_INTENT, origIntent); - intent.setDataAndType(origIntent.getData(), - origIntent.getType()); - //intent.fillIn(getIntent(), 0); - intent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP); - Log.i(LOG_TAG, - "Starting next activity with intent " + intent); - startActivity(intent); - } else { - Log.w(LOG_TAG, - "Unable to extract a component name from " + nextActivity); - } - } - - finish(); - } - - @Override - protected Void doInBackground(String... all_libs) { - // This will contain all unpack jobs - Map unpack_jobs = - new HashMap(all_libs.length); - - // This will contain all unpack filenames (so we can - // detect stale files in the unpack directory) - Set all_files = new HashSet(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(1)); - } catch (PackageManager.NameNotFoundException e) { - Log.e(LOG_TAG, "Package " + pkg + - " not found", e); - continue; - } catch (IOException e) { - // FIXME: show some sort of GUI error dialog - Log.e(LOG_TAG, - "Error opening ZIP for package " + pkg, e); - continue; - } - unpack_jobs.put(pkg, job); - } - job.paths.add(path); - } - - // Delete stale filenames from mUnpackDest - for (File file: mUnpackDest.listFiles()) { - if (!all_files.contains(file.getName())) { - Log.i(LOG_TAG, - "Deleting stale cached file " + file); - file.delete(); - } - } - - int total_size = 0; - for (UnpackJob job: unpack_jobs.values()) - total_size += job.UnpackSize(); - - publishProgress(0, total_size); - - mUnpackDest.mkdirs(); - - int progress = 0; - - for (UnpackJob job: unpack_jobs.values()) { - try { - ZipFile zipfile = job.zipfile; - for (String path: job.paths) { - ZipEntry zipentry = zipfile.getEntry(path); - if (zipentry == null) - throw new FileNotFoundException( - "Couldn't find " + path + " in zip"); - File dest = new File(mUnpackDest, new File(path).getName()); - if (dest.exists() && - dest.lastModified() == zipentry.getTime() && - dest.length() == zipentry.getSize()) { - // Already unpacked - progress += zipentry.getSize(); - } else { - if (dest.exists()) - Log.d(LOG_TAG, - "Replacing " + dest.getPath() + - " old.mtime=" + dest.lastModified() + - " new.mtime=" + zipentry.getTime() + - " old.size=" + dest.length() + - " new.size=" + zipentry.getSize()); - else - Log.i(LOG_TAG, - "Extracting " + zipentry.getName() + - " from " + zipfile.getName() + - " to " + dest.getPath()); - - long next_update = progress; - - InputStream in = zipfile.getInputStream(zipentry); - OutputStream out = new FileOutputStream(dest); - int len; - byte[] buffer = new byte[4096]; - while ((len = in.read(buffer)) != -1) { - out.write(buffer, 0, len); - progress += len; - if (progress >= next_update) { - publishProgress(progress, total_size); - // Arbitrary limit of 2% update steps - next_update += total_size / 50; - } - } - - in.close(); - out.close(); - dest.setLastModified(zipentry.getTime()); - } - publishProgress(progress, total_size); - } - - zipfile.close(); - } catch (IOException e) { - // FIXME: show some sort of GUI error dialog - Log.e(LOG_TAG, "Error unpacking plugin", e); - } - } - - if (progress != total_size) - Log.d(LOG_TAG, "Ended with progress " + progress + - " != total size " + total_size); - - setResult(RESULT_OK); - - return null; - } - } - - private class PluginBroadcastReciever extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - if (!intent.getAction() - .equals(ScummVMApplication.ACTION_PLUGIN_QUERY)) { - Log.e(LOG_TAG, - "Received unexpected action " + intent.getAction()); - return; - } - - Bundle extras = getResultExtras(false); - if (extras == null) { - // Nothing for us to do. - Unpacker.this.setResult(RESULT_OK); - finish(); - } - - ArrayList 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 unpack_libs = new ArrayList(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); - // Android 3.1 defaults to FLAG_EXCLUDE_STOPPED_PACKAGES, and since - // none of our plugins will ever be running, that is not helpful - intent.setFlags(FLAG_INCLUDE_STOPPED_PACKAGES); - sendOrderedBroadcast(intent, Manifest.permission.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 plugins = getPackageManager() - .queryBroadcastReceivers(intent, 0); - if (PLUGINS_ENABLED && plugins.isEmpty()) { - // No plugins installed - AlertDialog.Builder alert = new AlertDialog.Builder(this) - .setTitle(R.string.no_plugins_title) - .setMessage(R.string.no_plugins_found) - .setIcon(android.R.drawable.ic_dialog_alert) - .setOnCancelListener(new DialogInterface.OnCancelListener() { - public void onCancel(DialogInterface dialog) { - finish(); - } - }) - .setNegativeButton(R.string.quit, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - finish(); - } - }); - - final Uri uri = Uri.parse("market://search?q=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(LOG_TAG, - "Error starting market", e); - } - } - }); - } - - alert.show(); - - } else { - // Already have at least one plugin installed - initPlugins(); - } - } - - @Override - public void onStop() { - if (mUnpacker != null) - mUnpacker.cancel(true); - super.onStop(); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, - Intent data) { - switch (requestCode) { - case REQUEST_MARKET: - if (resultCode != RESULT_OK) - Log.w(LOG_TAG, "Market returned " + resultCode); - tryUnpack(); - break; - } - } - - private Bundle getMetaData() { - try { - ActivityInfo ai = getPackageManager() - .getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); - return ai.metaData; - } catch (PackageManager.NameNotFoundException e) { - Log.w(LOG_TAG, "Unable to find my own meta-data", e); - return new Bundle(); - } - } -} diff --git a/backends/platform/android/org/scummvm/scummvm/EditableSurfaceView.java b/backends/platform/android/org/scummvm/scummvm/EditableSurfaceView.java new file mode 100644 index 0000000000..b593fc6abf --- /dev/null +++ b/backends/platform/android/org/scummvm/scummvm/EditableSurfaceView.java @@ -0,0 +1,61 @@ +package org.scummvm.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 false; + } + + private class MyInputConnection extends BaseInputConnection { + public MyInputConnection() { + super(EditableSurfaceView.this, false); + } + + @Override + public boolean performEditorAction(int actionCode) { + if (actionCode == EditorInfo.IME_ACTION_DONE) { + InputMethodManager imm = (InputMethodManager) + getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(getWindowToken(), 0); + } + + // Sends enter key + return super.performEditorAction(actionCode); + } + } + + @Override + public InputConnection onCreateInputConnection(EditorInfo outAttrs) { + outAttrs.initialCapsMode = 0; + outAttrs.initialSelEnd = outAttrs.initialSelStart = -1; + outAttrs.inputType = (InputType.TYPE_CLASS_TEXT | + InputType.TYPE_TEXT_VARIATION_NORMAL | + InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE); + outAttrs.imeOptions = (EditorInfo.IME_ACTION_DONE | + EditorInfo.IME_FLAG_NO_EXTRACT_UI); + + return new MyInputConnection(); + } +} diff --git a/backends/platform/android/org/scummvm/scummvm/PluginProvider.java b/backends/platform/android/org/scummvm/scummvm/PluginProvider.java new file mode 100644 index 0000000000..0c43529f83 --- /dev/null +++ b/backends/platform/android/org/scummvm/scummvm/PluginProvider.java @@ -0,0 +1,53 @@ +package org.scummvm.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 { + private final static String LOG_TAG = "ScummVM"; + + public final static String META_UNPACK_LIB = + "org.scummvm.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(LOG_TAG, "Error finding my own info?", e); + return; + } + + String mylib = info.metaData.getString(META_UNPACK_LIB); + if (mylib != null) { + ArrayList 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); + } +} diff --git a/backends/platform/android/org/scummvm/scummvm/ScummVM.java b/backends/platform/android/org/scummvm/scummvm/ScummVM.java new file mode 100644 index 0000000000..3a25b54eeb --- /dev/null +++ b/backends/platform/android/org/scummvm/scummvm/ScummVM.java @@ -0,0 +1,451 @@ +package org.scummvm.scummvm; + +import android.util.Log; +import android.content.res.AssetManager; +import android.view.SurfaceHolder; +import android.media.AudioFormat; +import android.media.AudioManager; +import android.media.AudioTrack; + +import javax.microedition.khronos.opengles.GL10; +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.egl.EGLDisplay; +import javax.microedition.khronos.egl.EGLSurface; + +import java.io.File; +import java.util.LinkedHashMap; + +public abstract class ScummVM implements SurfaceHolder.Callback, Runnable { + final protected static String LOG_TAG = "ScummVM"; + final private AssetManager _asset_manager; + final private Object _sem_surface; + + private EGL10 _egl; + private EGLDisplay _egl_display = EGL10.EGL_NO_DISPLAY; + private EGLConfig _egl_config; + private EGLContext _egl_context = EGL10.EGL_NO_CONTEXT; + private EGLSurface _egl_surface = EGL10.EGL_NO_SURFACE; + + private SurfaceHolder _surface_holder; + private AudioTrack _audio_track; + private int _sample_rate = 0; + private int _buffer_size = 0; + + private String[] _args; + + final private native void create(AssetManager asset_manager, + EGL10 egl, EGLDisplay egl_display, + AudioTrack audio_track, + int sample_rate, int buffer_size); + final private native void destroy(); + final private native void setSurface(int width, int height); + final private native int main(String[] args); + + // pause the engine and all native threads + final public native void setPause(boolean pause); + final public native void enableZoning(boolean enable); + // Feed an event to ScummVM. Safe to call from other threads. + final public native void pushEvent(int type, int arg1, int arg2, int arg3, + int arg4, int arg5); + + // Callbacks from C++ peer instance + abstract protected void getDPI(float[] values); + abstract protected void displayMessageOnOSD(String msg); + abstract protected void setWindowCaption(String caption); + abstract protected String[] getPluginDirectories(); + abstract protected void showVirtualKeyboard(boolean enable); + abstract protected String[] getSysArchives(); + + public ScummVM(AssetManager asset_manager, SurfaceHolder holder) { + _asset_manager = asset_manager; + _sem_surface = new Object(); + + holder.addCallback(this); + } + + // SurfaceHolder callback + final public void surfaceCreated(SurfaceHolder holder) { + Log.d(LOG_TAG, "surfaceCreated"); + + // no need to do anything, surfaceChanged() will be called in any case + } + + // SurfaceHolder callback + final public void surfaceChanged(SurfaceHolder holder, int format, + int width, int height) { + // the orientation may reset on standby mode and the theme manager + // could assert when using a portrait resolution. so lets not do that. + if (height > width) { + Log.d(LOG_TAG, String.format("Ignoring surfaceChanged: %dx%d (%d)", + width, height, format)); + return; + } + + Log.d(LOG_TAG, String.format("surfaceChanged: %dx%d (%d)", + width, height, format)); + + synchronized(_sem_surface) { + _surface_holder = holder; + _sem_surface.notifyAll(); + } + + // store values for the native code + setSurface(width, height); + } + + // SurfaceHolder callback + final public void surfaceDestroyed(SurfaceHolder holder) { + Log.d(LOG_TAG, "surfaceDestroyed"); + + synchronized(_sem_surface) { + _surface_holder = null; + _sem_surface.notifyAll(); + } + + // clear values for the native code + setSurface(0, 0); + } + + final public void setArgs(String[] args) { + _args = args; + } + + final public void run() { + try { + initAudio(); + initEGL(); + + // wait for the surfaceChanged callback + synchronized(_sem_surface) { + while (_surface_holder == null) + _sem_surface.wait(); + } + } catch (Exception e) { + deinitEGL(); + deinitAudio(); + + throw new RuntimeException("Error preparing the ScummVM thread", e); + } + + create(_asset_manager, _egl, _egl_display, + _audio_track, _sample_rate, _buffer_size); + + int res = main(_args); + + destroy(); + + deinitEGL(); + deinitAudio(); + + // On exit, tear everything down for a fresh restart next time. + System.exit(res); + } + + final private void initEGL() throws Exception { + _egl = (EGL10)EGLContext.getEGL(); + _egl_display = _egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); + + int[] version = new int[2]; + _egl.eglInitialize(_egl_display, version); + + int[] num_config = new int[1]; + _egl.eglGetConfigs(_egl_display, null, 0, num_config); + + final int numConfigs = num_config[0]; + + if (numConfigs <= 0) + throw new IllegalArgumentException("No EGL configs"); + + EGLConfig[] configs = new EGLConfig[numConfigs]; + _egl.eglGetConfigs(_egl_display, configs, numConfigs, num_config); + + // Android's eglChooseConfig is busted in several versions and + // devices so we have to filter/rank the configs ourselves. + _egl_config = chooseEglConfig(configs); + + _egl_context = _egl.eglCreateContext(_egl_display, _egl_config, + EGL10.EGL_NO_CONTEXT, null); + + if (_egl_context == EGL10.EGL_NO_CONTEXT) + throw new Exception(String.format("Failed to create context: 0x%x", + _egl.eglGetError())); + } + + // Callback from C++ peer instance + final protected EGLSurface initSurface() throws Exception { + _egl_surface = _egl.eglCreateWindowSurface(_egl_display, _egl_config, + _surface_holder, null); + + if (_egl_surface == EGL10.EGL_NO_SURFACE) + throw new Exception(String.format( + "eglCreateWindowSurface failed: 0x%x", _egl.eglGetError())); + + _egl.eglMakeCurrent(_egl_display, _egl_surface, _egl_surface, + _egl_context); + + GL10 gl = (GL10)_egl_context.getGL(); + + Log.i(LOG_TAG, String.format("Using EGL %s (%s); GL %s/%s (%s)", + _egl.eglQueryString(_egl_display, EGL10.EGL_VERSION), + _egl.eglQueryString(_egl_display, EGL10.EGL_VENDOR), + gl.glGetString(GL10.GL_VERSION), + gl.glGetString(GL10.GL_RENDERER), + gl.glGetString(GL10.GL_VENDOR))); + + return _egl_surface; + } + + // Callback from C++ peer instance + final protected void deinitSurface() { + if (_egl_display != EGL10.EGL_NO_DISPLAY) { + _egl.eglMakeCurrent(_egl_display, EGL10.EGL_NO_SURFACE, + EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); + + if (_egl_surface != EGL10.EGL_NO_SURFACE) + _egl.eglDestroySurface(_egl_display, _egl_surface); + } + + _egl_surface = EGL10.EGL_NO_SURFACE; + } + + final private void deinitEGL() { + if (_egl_display != EGL10.EGL_NO_DISPLAY) { + _egl.eglMakeCurrent(_egl_display, EGL10.EGL_NO_SURFACE, + EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); + + if (_egl_surface != EGL10.EGL_NO_SURFACE) + _egl.eglDestroySurface(_egl_display, _egl_surface); + + if (_egl_context != EGL10.EGL_NO_CONTEXT) + _egl.eglDestroyContext(_egl_display, _egl_context); + + _egl.eglTerminate(_egl_display); + } + + _egl_surface = EGL10.EGL_NO_SURFACE; + _egl_context = EGL10.EGL_NO_CONTEXT; + _egl_config = null; + _egl_display = EGL10.EGL_NO_DISPLAY; + _egl = null; + } + + final private void initAudio() throws Exception { + _sample_rate = AudioTrack.getNativeOutputSampleRate( + AudioManager.STREAM_MUSIC); + _buffer_size = AudioTrack.getMinBufferSize(_sample_rate, + AudioFormat.CHANNEL_CONFIGURATION_STEREO, + AudioFormat.ENCODING_PCM_16BIT); + + // ~50ms + int buffer_size_want = (_sample_rate * 2 * 2 / 20) & ~1023; + + if (_buffer_size < buffer_size_want) { + Log.w(LOG_TAG, String.format( + "adjusting audio buffer size (was: %d)", _buffer_size)); + + _buffer_size = buffer_size_want; + } + + Log.i(LOG_TAG, String.format("Using %d bytes buffer for %dHz audio", + _buffer_size, _sample_rate)); + + _audio_track = new AudioTrack(AudioManager.STREAM_MUSIC, + _sample_rate, + AudioFormat.CHANNEL_CONFIGURATION_STEREO, + AudioFormat.ENCODING_PCM_16BIT, + _buffer_size, + AudioTrack.MODE_STREAM); + + if (_audio_track.getState() != AudioTrack.STATE_INITIALIZED) + throw new Exception( + String.format("Error initializing AudioTrack: %d", + _audio_track.getState())); + } + + final private void deinitAudio() { + if (_audio_track != null) + _audio_track.stop(); + + _audio_track = null; + _buffer_size = 0; + _sample_rate = 0; + } + + private static final int[] s_eglAttribs = { + EGL10.EGL_CONFIG_ID, + EGL10.EGL_BUFFER_SIZE, + EGL10.EGL_RED_SIZE, + EGL10.EGL_GREEN_SIZE, + EGL10.EGL_BLUE_SIZE, + EGL10.EGL_ALPHA_SIZE, + EGL10.EGL_CONFIG_CAVEAT, + EGL10.EGL_DEPTH_SIZE, + EGL10.EGL_LEVEL, + EGL10.EGL_MAX_PBUFFER_WIDTH, + EGL10.EGL_MAX_PBUFFER_HEIGHT, + EGL10.EGL_MAX_PBUFFER_PIXELS, + EGL10.EGL_NATIVE_RENDERABLE, + EGL10.EGL_NATIVE_VISUAL_ID, + EGL10.EGL_NATIVE_VISUAL_TYPE, + EGL10.EGL_SAMPLE_BUFFERS, + EGL10.EGL_SAMPLES, + EGL10.EGL_STENCIL_SIZE, + EGL10.EGL_SURFACE_TYPE, + EGL10.EGL_TRANSPARENT_TYPE, + EGL10.EGL_TRANSPARENT_RED_VALUE, + EGL10.EGL_TRANSPARENT_GREEN_VALUE, + EGL10.EGL_TRANSPARENT_BLUE_VALUE + }; + + final private class EglAttribs extends LinkedHashMap { + public EglAttribs(EGLConfig config) { + super(s_eglAttribs.length); + + int[] value = new int[1]; + + for (int i : s_eglAttribs) { + _egl.eglGetConfigAttrib(_egl_display, config, i, value); + + put(i, value[0]); + } + } + + private int weightBits(int attr, int size) { + final int value = get(attr); + + int score = 0; + + if (value == size || (size > 0 && value > size)) + score += 10; + + // penalize for wasted bits + score -= value - size; + + return score; + } + + public int weight() { + int score = 10000; + + if (get(EGL10.EGL_CONFIG_CAVEAT) != EGL10.EGL_NONE) + score -= 1000; + + // less MSAA is better + score -= get(EGL10.EGL_SAMPLES) * 100; + + // Must be at least 565, but then smaller is better + score += weightBits(EGL10.EGL_RED_SIZE, 5); + score += weightBits(EGL10.EGL_GREEN_SIZE, 6); + score += weightBits(EGL10.EGL_BLUE_SIZE, 5); + score += weightBits(EGL10.EGL_ALPHA_SIZE, 0); + score += weightBits(EGL10.EGL_DEPTH_SIZE, 0); + score += weightBits(EGL10.EGL_STENCIL_SIZE, 0); + + return score; + } + + public String toString() { + String s; + + if (get(EGL10.EGL_ALPHA_SIZE) > 0) + s = String.format("[%d] RGBA%d%d%d%d", + get(EGL10.EGL_CONFIG_ID), + get(EGL10.EGL_RED_SIZE), + get(EGL10.EGL_GREEN_SIZE), + get(EGL10.EGL_BLUE_SIZE), + get(EGL10.EGL_ALPHA_SIZE)); + else + s = String.format("[%d] RGB%d%d%d", + get(EGL10.EGL_CONFIG_ID), + get(EGL10.EGL_RED_SIZE), + get(EGL10.EGL_GREEN_SIZE), + get(EGL10.EGL_BLUE_SIZE)); + + if (get(EGL10.EGL_DEPTH_SIZE) > 0) + s += String.format(" D%d", get(EGL10.EGL_DEPTH_SIZE)); + + if (get(EGL10.EGL_STENCIL_SIZE) > 0) + s += String.format(" S%d", get(EGL10.EGL_STENCIL_SIZE)); + + if (get(EGL10.EGL_SAMPLES) > 0) + s += String.format(" MSAAx%d", get(EGL10.EGL_SAMPLES)); + + if ((get(EGL10.EGL_SURFACE_TYPE) & EGL10.EGL_WINDOW_BIT) > 0) + s += " W"; + if ((get(EGL10.EGL_SURFACE_TYPE) & EGL10.EGL_PBUFFER_BIT) > 0) + s += " P"; + if ((get(EGL10.EGL_SURFACE_TYPE) & EGL10.EGL_PIXMAP_BIT) > 0) + s += " X"; + + switch (get(EGL10.EGL_CONFIG_CAVEAT)) { + case EGL10.EGL_NONE: + break; + + case EGL10.EGL_SLOW_CONFIG: + s += " SLOW"; + break; + + case EGL10.EGL_NON_CONFORMANT_CONFIG: + s += " NON_CONFORMANT"; + + default: + s += String.format(" unknown CAVEAT 0x%x", + get(EGL10.EGL_CONFIG_CAVEAT)); + } + + return s; + } + }; + + final private EGLConfig chooseEglConfig(EGLConfig[] configs) { + EGLConfig res = configs[0]; + int bestScore = -1; + + Log.d(LOG_TAG, "EGL configs:"); + + for (EGLConfig config : configs) { + EglAttribs attr = new EglAttribs(config); + + // must have + if ((attr.get(EGL10.EGL_SURFACE_TYPE) & EGL10.EGL_WINDOW_BIT) == 0) + continue; + + int score = attr.weight(); + + Log.d(LOG_TAG, String.format("%s (%d)", attr.toString(), score)); + + if (score > bestScore) { + res = config; + bestScore = score; + } + } + + if (bestScore < 0) + Log.e(LOG_TAG, + "Unable to find an acceptable EGL config, expect badness."); + + Log.d(LOG_TAG, String.format("Chosen EGL config: %s", + new EglAttribs(res).toString())); + + return res; + } + + static { + // For grabbing with gdb... + final boolean sleep_for_debugger = false; + if (sleep_for_debugger) { + try { + Thread.sleep(20 * 1000); + } catch (InterruptedException e) { + } + } + + File cache_dir = ScummVMApplication.getLastCacheDir(); + String libname = System.mapLibraryName("scummvm"); + File libpath = new File(cache_dir, libname); + + System.load(libpath.getPath()); + } +} diff --git a/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java b/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java new file mode 100644 index 0000000000..a41e843323 --- /dev/null +++ b/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java @@ -0,0 +1,224 @@ +package org.scummvm.scummvm; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.media.AudioManager; +import android.os.Bundle; +import android.os.Environment; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.SurfaceView; +import android.view.SurfaceHolder; +import android.view.MotionEvent; +import android.view.inputmethod.InputMethodManager; +import android.widget.Toast; + +public class ScummVMActivity extends Activity { + + private class MyScummVM extends ScummVM { + private boolean usingSmallScreen() { + // Multiple screen sizes came in with Android 1.6. Have + // to use reflection in order to continue supporting 1.5 + // devices :( + DisplayMetrics metrics = new DisplayMetrics(); + getWindowManager().getDefaultDisplay().getMetrics(metrics); + + try { + // This 'density' term is very confusing. + int DENSITY_LOW = metrics.getClass().getField("DENSITY_LOW").getInt(null); + int densityDpi = metrics.getClass().getField("densityDpi").getInt(metrics); + return densityDpi <= DENSITY_LOW; + } catch (Exception e) { + return false; + } + } + + public MyScummVM(SurfaceHolder holder) { + super(ScummVMActivity.this.getAssets(), holder); + + // Enable ScummVM zoning on 'small' screens. + // FIXME make this optional for the user + // disabled for now since it crops too much + //enableZoning(usingSmallScreen()); + } + + @Override + protected void getDPI(float[] values) { + DisplayMetrics metrics = new DisplayMetrics(); + getWindowManager().getDefaultDisplay().getMetrics(metrics); + + values[0] = metrics.xdpi; + values[1] = metrics.ydpi; + } + + @Override + protected void displayMessageOnOSD(String msg) { + Log.i(LOG_TAG, "OSD: " + msg); + Toast.makeText(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) { + runOnUiThread(new Runnable() { + public void run() { + showKeyboard(enable); + } + }); + } + + @Override + protected String[] getSysArchives() { + return new String[0]; + } + + } + + private MyScummVM _scummvm; + private ScummVMEvents _events; + private Thread _scummvm_thread; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setVolumeControlStream(AudioManager.STREAM_MUSIC); + + setContentView(R.layout.main); + takeKeyEvents(true); + + // This is a common enough error that we should warn about it + // explicitly. + if (!Environment.getExternalStorageDirectory().canRead()) { + new AlertDialog.Builder(this) + .setTitle(R.string.no_sdcard_title) + .setIcon(android.R.drawable.ic_dialog_alert) + .setMessage(R.string.no_sdcard) + .setNegativeButton(R.string.quit, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, + int which) { + finish(); + } + }) + .show(); + + return; + } + + SurfaceView main_surface = (SurfaceView)findViewById(R.id.main_surface); + + main_surface.requestFocus(); + + getFilesDir().mkdirs(); + + // Start ScummVM + _scummvm = new MyScummVM(main_surface.getHolder()); + + _scummvm.setArgs(new String[] { + "ScummVM", + "--config=" + getFileStreamPath("scummvmrc").getPath(), + "--path=" + Environment.getExternalStorageDirectory().getPath(), + "--gui-theme=scummmodern", + "--savepath=" + getDir("saves", 0).getPath() + }); + + _events = new ScummVMEvents(this, _scummvm); + + main_surface.setOnKeyListener(_events); + main_surface.setOnTouchListener(_events); + + _scummvm_thread = new Thread(_scummvm, "ScummVM"); + _scummvm_thread.start(); + } + + @Override + public void onStart() { + Log.d(ScummVM.LOG_TAG, "onStart"); + + super.onStart(); + } + + @Override + public void onResume() { + Log.d(ScummVM.LOG_TAG, "onResume"); + + super.onResume(); + + if (_scummvm != null) + _scummvm.setPause(false); + } + + @Override + public void onPause() { + Log.d(ScummVM.LOG_TAG, "onPause"); + + super.onPause(); + + if (_scummvm != null) + _scummvm.setPause(true); + } + + @Override + public void onStop() { + Log.d(ScummVM.LOG_TAG, "onStop"); + + super.onStop(); + } + + @Override + public void onDestroy() { + Log.d(ScummVM.LOG_TAG, "onDestroy"); + + super.onDestroy(); + + if (_events != null) { + _events.sendQuitEvent(); + + try { + // 1s timeout + _scummvm_thread.join(1000); + } catch (InterruptedException e) { + Log.i(ScummVM.LOG_TAG, "Error while joining ScummVM thread", e); + } + + _scummvm = null; + } + } + + @Override + public boolean onTrackballEvent(MotionEvent e) { + if (_events != null) + return _events.onTrackballEvent(e); + + return false; + } + + private void showKeyboard(boolean show) { + SurfaceView main_surface = (SurfaceView)findViewById(R.id.main_surface); + InputMethodManager imm = (InputMethodManager) + getSystemService(INPUT_METHOD_SERVICE); + + if (show) + imm.showSoftInput(main_surface, InputMethodManager.SHOW_IMPLICIT); + else + imm.hideSoftInputFromWindow(main_surface.getWindowToken(), + InputMethodManager.HIDE_IMPLICIT_ONLY); + } +} diff --git a/backends/platform/android/org/scummvm/scummvm/ScummVMApplication.java b/backends/platform/android/org/scummvm/scummvm/ScummVMApplication.java new file mode 100644 index 0000000000..9241cba918 --- /dev/null +++ b/backends/platform/android/org/scummvm/scummvm/ScummVMApplication.java @@ -0,0 +1,30 @@ +package org.scummvm.scummvm; + +import android.app.Application; + +import java.io.File; + +public class ScummVMApplication extends Application { + public final static String ACTION_PLUGIN_QUERY = "org.scummvm.scummvm.action.PLUGIN_QUERY"; + public final static String EXTRA_UNPACK_LIBS = "org.scummvm.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; + } +} diff --git a/backends/platform/android/org/scummvm/scummvm/ScummVMEvents.java b/backends/platform/android/org/scummvm/scummvm/ScummVMEvents.java new file mode 100644 index 0000000000..86227b9352 --- /dev/null +++ b/backends/platform/android/org/scummvm/scummvm/ScummVMEvents.java @@ -0,0 +1,231 @@ +package org.scummvm.scummvm; + +import android.os.Handler; +import android.os.Message; +import android.util.Log; +import android.content.Context; +import android.view.KeyEvent; +import android.view.KeyCharacterMap; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.GestureDetector; +import android.view.inputmethod.InputMethodManager; + +public class ScummVMEvents implements + android.view.View.OnKeyListener, + android.view.View.OnTouchListener, + android.view.GestureDetector.OnGestureListener, + android.view.GestureDetector.OnDoubleTapListener { + + public static final int JE_SYS_KEY = 0; + public static final int JE_KEY = 1; + public static final int JE_DPAD = 2; + public static final int JE_DOWN = 3; + public static final int JE_SCROLL = 4; + public static final int JE_TAP = 5; + public static final int JE_DOUBLE_TAP = 6; + public static final int JE_MULTI = 7; + public static final int JE_BALL = 8; + public static final int JE_QUIT = 0x1000; + + final protected Context _context; + final protected ScummVM _scummvm; + final protected GestureDetector _gd; + final protected int _longPress; + + public ScummVMEvents(Context context, ScummVM scummvm) { + _context = context; + _scummvm = scummvm; + + _gd = new GestureDetector(context, this); + _gd.setOnDoubleTapListener(this); + _gd.setIsLongpressEnabled(false); + + _longPress = ViewConfiguration.getLongPressTimeout(); + } + + final public void sendQuitEvent() { + _scummvm.pushEvent(JE_QUIT, 0, 0, 0, 0, 0); + } + + public boolean onTrackballEvent(MotionEvent e) { + _scummvm.pushEvent(JE_BALL, e.getAction(), + (int)(e.getX() * e.getXPrecision() * 100), + (int)(e.getY() * e.getYPrecision() * 100), + 0, 0); + return true; + } + + final static int MSG_MENU_LONG_PRESS = 1; + + final private Handler keyHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + if (msg.what == MSG_MENU_LONG_PRESS) { + InputMethodManager imm = (InputMethodManager) + _context.getSystemService(_context.INPUT_METHOD_SERVICE); + + if (imm != null) + imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); + } + } + }; + + // OnKeyListener + final public boolean onKey(View v, int keyCode, KeyEvent e) { + final int action = e.getAction(); + + if (e.isSystem()) { + // filter what we handle + switch (keyCode) { + case KeyEvent.KEYCODE_BACK: + case KeyEvent.KEYCODE_MENU: + case KeyEvent.KEYCODE_CAMERA: + case KeyEvent.KEYCODE_SEARCH: + break; + + default: + return false; + } + + // no repeats for system keys + if (e.getRepeatCount() > 0) + return false; + + // Have to reimplement hold-down-menu-brings-up-softkeybd + // ourselves, since we are otherwise hijacking the menu key :( + // See com.android.internal.policy.impl.PhoneWindow.onKeyDownPanel() + // for the usual Android implementation of this feature. + if (keyCode == KeyEvent.KEYCODE_MENU) { + final boolean fired = + !keyHandler.hasMessages(MSG_MENU_LONG_PRESS); + + keyHandler.removeMessages(MSG_MENU_LONG_PRESS); + + if (action == KeyEvent.ACTION_DOWN) { + keyHandler.sendMessageDelayed(keyHandler.obtainMessage( + MSG_MENU_LONG_PRESS), _longPress); + return true; + } + + if (fired) + return true; + + // only send up events of the menu button to the native side + if (action != KeyEvent.ACTION_UP) + return true; + } + + _scummvm.pushEvent(JE_SYS_KEY, action, keyCode, 0, 0, 0); + + return true; + } + + // sequence of characters + if (action == KeyEvent.ACTION_MULTIPLE && + keyCode == KeyEvent.KEYCODE_UNKNOWN) { + final KeyCharacterMap m = KeyCharacterMap.load(e.getDeviceId()); + final KeyEvent[] es = m.getEvents(e.getCharacters().toCharArray()); + + if (es == null) + return true; + + for (KeyEvent s : es) { + _scummvm.pushEvent(JE_KEY, s.getAction(), s.getKeyCode(), + s.getUnicodeChar() & KeyCharacterMap.COMBINING_ACCENT_MASK, + s.getMetaState(), s.getRepeatCount()); + } + + return true; + } + + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_UP: + case KeyEvent.KEYCODE_DPAD_DOWN: + case KeyEvent.KEYCODE_DPAD_LEFT: + case KeyEvent.KEYCODE_DPAD_RIGHT: + case KeyEvent.KEYCODE_DPAD_CENTER: + _scummvm.pushEvent(JE_DPAD, action, keyCode, + (int)(e.getEventTime() - e.getDownTime()), + e.getRepeatCount(), 0); + return true; + } + + _scummvm.pushEvent(JE_KEY, action, keyCode, + e.getUnicodeChar() & KeyCharacterMap.COMBINING_ACCENT_MASK, + e.getMetaState(), e.getRepeatCount()); + + return true; + } + + // OnTouchListener + final public boolean onTouch(View v, MotionEvent e) { + final int action = e.getAction(); + + // constants from APIv5: + // (action & ACTION_POINTER_INDEX_MASK) >> ACTION_POINTER_INDEX_SHIFT + final int pointer = (action & 0xff00) >> 8; + + if (pointer > 0) { + _scummvm.pushEvent(JE_MULTI, pointer, action & 0xff, // ACTION_MASK + (int)e.getX(), (int)e.getY(), 0); + return true; + } + + return _gd.onTouchEvent(e); + } + + // OnGestureListener + final public boolean onDown(MotionEvent e) { + _scummvm.pushEvent(JE_DOWN, (int)e.getX(), (int)e.getY(), 0, 0, 0); + return true; + } + + final public boolean onFling(MotionEvent e1, MotionEvent e2, + float velocityX, float velocityY) { + //Log.d(ScummVM.LOG_TAG, String.format("onFling: %s -> %s (%.3f %.3f)", + // e1.toString(), e2.toString(), + // velocityX, velocityY)); + + return true; + } + + final public void onLongPress(MotionEvent e) { + // disabled, interferes with drag&drop + } + + final public boolean onScroll(MotionEvent e1, MotionEvent e2, + float distanceX, float distanceY) { + _scummvm.pushEvent(JE_SCROLL, (int)e1.getX(), (int)e1.getY(), + (int)e2.getX(), (int)e2.getY(), 0); + + return true; + } + + final public void onShowPress(MotionEvent e) { + } + + final public boolean onSingleTapUp(MotionEvent e) { + _scummvm.pushEvent(JE_TAP, (int)e.getX(), (int)e.getY(), + (int)(e.getEventTime() - e.getDownTime()), 0, 0); + + return true; + } + + // OnDoubleTapListener + final public boolean onDoubleTap(MotionEvent e) { + return true; + } + + final public boolean onDoubleTapEvent(MotionEvent e) { + _scummvm.pushEvent(JE_DOUBLE_TAP, (int)e.getX(), (int)e.getY(), + e.getAction(), 0, 0); + + return true; + } + + final public boolean onSingleTapConfirmed(MotionEvent e) { + return true; + } +} diff --git a/backends/platform/android/org/scummvm/scummvm/Unpacker.java b/backends/platform/android/org/scummvm/scummvm/Unpacker.java new file mode 100644 index 0000000000..4564d96622 --- /dev/null +++ b/backends/platform/android/org/scummvm/scummvm/Unpacker.java @@ -0,0 +1,378 @@ +package org.scummvm.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 { + protected final static String LOG_TAG = "ScummVM"; + // TODO don't hardcode this + private final static boolean PLUGINS_ENABLED = false; + private final static String META_NEXT_ACTIVITY = + "org.scummvm.unpacker.nextActivity"; + private ProgressBar mProgress; + private File mUnpackDest; // location to unpack into + private AsyncTask mUnpacker; + private final static int REQUEST_MARKET = 1; + + // Android 3.1+ only + public static final int FLAG_INCLUDE_STOPPED_PACKAGES = 32; + + private static class UnpackJob { + public ZipFile zipfile; + public Set paths; + + public UnpackJob(ZipFile zipfile, Set 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 { + @Override + protected void onProgressUpdate(Integer... progress) { + mProgress.setIndeterminate(false); + mProgress.setMax(progress[1]); + mProgress.setProgress(progress[0]); + mProgress.postInvalidate(); + } + + @Override + protected void onPostExecute(Void result) { + Bundle md = getMetaData(); + String nextActivity = md.getString(META_NEXT_ACTIVITY); + if (nextActivity != null) { + final ComponentName cn = + ComponentName.unflattenFromString(nextActivity); + if (cn != null) { + final Intent origIntent = getIntent(); + Intent intent = new Intent(); + intent.setComponent(cn); + if (origIntent.getExtras() != null) + intent.putExtras(origIntent.getExtras()); + intent.putExtra(Intent.EXTRA_INTENT, origIntent); + intent.setDataAndType(origIntent.getData(), + origIntent.getType()); + //intent.fillIn(getIntent(), 0); + intent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP); + Log.i(LOG_TAG, + "Starting next activity with intent " + intent); + startActivity(intent); + } else { + Log.w(LOG_TAG, + "Unable to extract a component name from " + nextActivity); + } + } + + finish(); + } + + @Override + protected Void doInBackground(String... all_libs) { + // This will contain all unpack jobs + Map unpack_jobs = + new HashMap(all_libs.length); + + // This will contain all unpack filenames (so we can + // detect stale files in the unpack directory) + Set all_files = new HashSet(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(1)); + } catch (PackageManager.NameNotFoundException e) { + Log.e(LOG_TAG, "Package " + pkg + + " not found", e); + continue; + } catch (IOException e) { + // FIXME: show some sort of GUI error dialog + Log.e(LOG_TAG, + "Error opening ZIP for package " + pkg, e); + continue; + } + unpack_jobs.put(pkg, job); + } + job.paths.add(path); + } + + // Delete stale filenames from mUnpackDest + for (File file: mUnpackDest.listFiles()) { + if (!all_files.contains(file.getName())) { + Log.i(LOG_TAG, + "Deleting stale cached file " + file); + file.delete(); + } + } + + int total_size = 0; + for (UnpackJob job: unpack_jobs.values()) + total_size += job.UnpackSize(); + + publishProgress(0, total_size); + + mUnpackDest.mkdirs(); + + int progress = 0; + + for (UnpackJob job: unpack_jobs.values()) { + try { + ZipFile zipfile = job.zipfile; + for (String path: job.paths) { + ZipEntry zipentry = zipfile.getEntry(path); + if (zipentry == null) + throw new FileNotFoundException( + "Couldn't find " + path + " in zip"); + File dest = new File(mUnpackDest, new File(path).getName()); + if (dest.exists() && + dest.lastModified() == zipentry.getTime() && + dest.length() == zipentry.getSize()) { + // Already unpacked + progress += zipentry.getSize(); + } else { + if (dest.exists()) + Log.d(LOG_TAG, + "Replacing " + dest.getPath() + + " old.mtime=" + dest.lastModified() + + " new.mtime=" + zipentry.getTime() + + " old.size=" + dest.length() + + " new.size=" + zipentry.getSize()); + else + Log.i(LOG_TAG, + "Extracting " + zipentry.getName() + + " from " + zipfile.getName() + + " to " + dest.getPath()); + + long next_update = progress; + + InputStream in = zipfile.getInputStream(zipentry); + OutputStream out = new FileOutputStream(dest); + int len; + byte[] buffer = new byte[4096]; + while ((len = in.read(buffer)) != -1) { + out.write(buffer, 0, len); + progress += len; + if (progress >= next_update) { + publishProgress(progress, total_size); + // Arbitrary limit of 2% update steps + next_update += total_size / 50; + } + } + + in.close(); + out.close(); + dest.setLastModified(zipentry.getTime()); + } + publishProgress(progress, total_size); + } + + zipfile.close(); + } catch (IOException e) { + // FIXME: show some sort of GUI error dialog + Log.e(LOG_TAG, "Error unpacking plugin", e); + } + } + + if (progress != total_size) + Log.d(LOG_TAG, "Ended with progress " + progress + + " != total size " + total_size); + + setResult(RESULT_OK); + + return null; + } + } + + private class PluginBroadcastReciever extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (!intent.getAction() + .equals(ScummVMApplication.ACTION_PLUGIN_QUERY)) { + Log.e(LOG_TAG, + "Received unexpected action " + intent.getAction()); + return; + } + + Bundle extras = getResultExtras(false); + if (extras == null) { + // Nothing for us to do. + Unpacker.this.setResult(RESULT_OK); + finish(); + } + + ArrayList 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 unpack_libs = new ArrayList(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); + // Android 3.1 defaults to FLAG_EXCLUDE_STOPPED_PACKAGES, and since + // none of our plugins will ever be running, that is not helpful + intent.setFlags(FLAG_INCLUDE_STOPPED_PACKAGES); + sendOrderedBroadcast(intent, Manifest.permission.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 plugins = getPackageManager() + .queryBroadcastReceivers(intent, 0); + if (PLUGINS_ENABLED && plugins.isEmpty()) { + // No plugins installed + AlertDialog.Builder alert = new AlertDialog.Builder(this) + .setTitle(R.string.no_plugins_title) + .setMessage(R.string.no_plugins_found) + .setIcon(android.R.drawable.ic_dialog_alert) + .setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + finish(); + } + }) + .setNegativeButton(R.string.quit, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }); + + final Uri uri = Uri.parse("market://search?q=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(LOG_TAG, + "Error starting market", e); + } + } + }); + } + + alert.show(); + + } else { + // Already have at least one plugin installed + initPlugins(); + } + } + + @Override + public void onStop() { + if (mUnpacker != null) + mUnpacker.cancel(true); + super.onStop(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, + Intent data) { + switch (requestCode) { + case REQUEST_MARKET: + if (resultCode != RESULT_OK) + Log.w(LOG_TAG, "Market returned " + resultCode); + tryUnpack(); + break; + } + } + + private Bundle getMetaData() { + try { + ActivityInfo ai = getPackageManager() + .getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); + return ai.metaData; + } catch (PackageManager.NameNotFoundException e) { + Log.w(LOG_TAG, "Unable to find my own meta-data", e); + return new Bundle(); + } + } +} diff --git a/dists/android/AndroidManifest.xml b/dists/android/AndroidManifest.xml index a25048fe28..e7778fdf61 100644 --- a/dists/android/AndroidManifest.xml +++ b/dists/android/AndroidManifest.xml @@ -2,11 +2,11 @@ + android:sharedUserId="org.scummvm.scummvm"> @@ -31,8 +31,8 @@ android:theme="@android:style/Theme.NoTitleBar.Fullscreen" android:screenOrientation="landscape" android:configChanges="orientation|keyboardHidden"> - + @@ -40,7 +40,7 @@ - diff --git a/dists/android/AndroidManifest.xml.in b/dists/android/AndroidManifest.xml.in index 32444a03f0..8f7887eaf5 100644 --- a/dists/android/AndroidManifest.xml.in +++ b/dists/android/AndroidManifest.xml.in @@ -2,11 +2,11 @@ + android:sharedUserId="org.scummvm.scummvm"> @@ -31,8 +31,8 @@ android:theme="@android:style/Theme.NoTitleBar.Fullscreen" android:screenOrientation="landscape" android:configChanges="orientation|keyboardHidden"> - + @@ -40,7 +40,7 @@ - diff --git a/dists/android/plugin-manifest.xml b/dists/android/plugin-manifest.xml index 728beb1de3..51b39be3b1 100644 --- a/dists/android/plugin-manifest.xml +++ b/dists/android/plugin-manifest.xml @@ -1,29 +1,29 @@ + android:sharedUserId="org.scummvm.scummvm"> - + - + - - + diff --git a/dists/android/plugin-manifest.xml.in b/dists/android/plugin-manifest.xml.in index ab0a63a0cf..4b429097ae 100644 --- a/dists/android/plugin-manifest.xml.in +++ b/dists/android/plugin-manifest.xml.in @@ -1,29 +1,29 @@ + android:sharedUserId="org.scummvm.scummvm"> - + - + - - + diff --git a/dists/android/res/layout/main.xml b/dists/android/res/layout/main.xml index 7b633c416d..8b0d515d62 100644 --- a/dists/android/res/layout/main.xml +++ b/dists/android/res/layout/main.xml @@ -1,6 +1,6 @@ -