aboutsummaryrefslogtreecommitdiff
path: root/backends/platform/android/org/scummvm
diff options
context:
space:
mode:
authorAlyssa Milburn2011-10-25 21:18:42 +0200
committerAlyssa Milburn2011-10-25 21:18:42 +0200
commit44b7f3aed52b1213995f9e19e4395f4350997b01 (patch)
tree88e3b4711c2da6524d9b5dbb423083c08ab1fb8f /backends/platform/android/org/scummvm
parent2967e2cd9172b2d2492e46c33968013b2bcbdbc1 (diff)
downloadscummvm-rg350-44b7f3aed52b1213995f9e19e4395f4350997b01.tar.gz
scummvm-rg350-44b7f3aed52b1213995f9e19e4395f4350997b01.tar.bz2
scummvm-rg350-44b7f3aed52b1213995f9e19e4395f4350997b01.zip
ANDROID: Move from org.inodes.gus to org.scummvm.
Diffstat (limited to 'backends/platform/android/org/scummvm')
-rw-r--r--backends/platform/android/org/scummvm/scummvm/EditableSurfaceView.java61
-rw-r--r--backends/platform/android/org/scummvm/scummvm/PluginProvider.java53
-rw-r--r--backends/platform/android/org/scummvm/scummvm/ScummVM.java451
-rw-r--r--backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java224
-rw-r--r--backends/platform/android/org/scummvm/scummvm/ScummVMApplication.java30
-rw-r--r--backends/platform/android/org/scummvm/scummvm/ScummVMEvents.java231
-rw-r--r--backends/platform/android/org/scummvm/scummvm/Unpacker.java378
7 files changed, 1428 insertions, 0 deletions
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<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);
+ }
+}
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<Integer, Integer> {
+ public EglAttribs(EGLConfig config) {
+ super(s_eglAttribs.length);
+
+ int[] value = new int[1];
+
+ for (int i : s_eglAttribs) {
+ _egl.eglGetConfigAttrib(_egl_display, config, i, value);
+
+ put(i, value[0]);
+ }
+ }
+
+ private int weightBits(int attr, int size) {
+ final int value = get(attr);
+
+ int score = 0;
+
+ if (value == size || (size > 0 && value > size))
+ score += 10;
+
+ // penalize for wasted bits
+ score -= value - size;
+
+ return score;
+ }
+
+ public int weight() {
+ int score = 10000;
+
+ if (get(EGL10.EGL_CONFIG_CAVEAT) != EGL10.EGL_NONE)
+ score -= 1000;
+
+ // less MSAA is better
+ score -= get(EGL10.EGL_SAMPLES) * 100;
+
+ // Must be at least 565, but then smaller is better
+ score += weightBits(EGL10.EGL_RED_SIZE, 5);
+ score += weightBits(EGL10.EGL_GREEN_SIZE, 6);
+ score += weightBits(EGL10.EGL_BLUE_SIZE, 5);
+ score += weightBits(EGL10.EGL_ALPHA_SIZE, 0);
+ score += weightBits(EGL10.EGL_DEPTH_SIZE, 0);
+ score += weightBits(EGL10.EGL_STENCIL_SIZE, 0);
+
+ return score;
+ }
+
+ public String toString() {
+ String s;
+
+ if (get(EGL10.EGL_ALPHA_SIZE) > 0)
+ s = String.format("[%d] RGBA%d%d%d%d",
+ get(EGL10.EGL_CONFIG_ID),
+ get(EGL10.EGL_RED_SIZE),
+ get(EGL10.EGL_GREEN_SIZE),
+ get(EGL10.EGL_BLUE_SIZE),
+ get(EGL10.EGL_ALPHA_SIZE));
+ else
+ s = String.format("[%d] RGB%d%d%d",
+ get(EGL10.EGL_CONFIG_ID),
+ get(EGL10.EGL_RED_SIZE),
+ get(EGL10.EGL_GREEN_SIZE),
+ get(EGL10.EGL_BLUE_SIZE));
+
+ if (get(EGL10.EGL_DEPTH_SIZE) > 0)
+ s += String.format(" D%d", get(EGL10.EGL_DEPTH_SIZE));
+
+ if (get(EGL10.EGL_STENCIL_SIZE) > 0)
+ s += String.format(" S%d", get(EGL10.EGL_STENCIL_SIZE));
+
+ if (get(EGL10.EGL_SAMPLES) > 0)
+ s += String.format(" MSAAx%d", get(EGL10.EGL_SAMPLES));
+
+ if ((get(EGL10.EGL_SURFACE_TYPE) & EGL10.EGL_WINDOW_BIT) > 0)
+ s += " W";
+ if ((get(EGL10.EGL_SURFACE_TYPE) & EGL10.EGL_PBUFFER_BIT) > 0)
+ s += " P";
+ if ((get(EGL10.EGL_SURFACE_TYPE) & EGL10.EGL_PIXMAP_BIT) > 0)
+ s += " X";
+
+ switch (get(EGL10.EGL_CONFIG_CAVEAT)) {
+ case EGL10.EGL_NONE:
+ break;
+
+ case EGL10.EGL_SLOW_CONFIG:
+ s += " SLOW";
+ break;
+
+ case EGL10.EGL_NON_CONFORMANT_CONFIG:
+ s += " NON_CONFORMANT";
+
+ default:
+ s += String.format(" unknown CAVEAT 0x%x",
+ get(EGL10.EGL_CONFIG_CAVEAT));
+ }
+
+ return s;
+ }
+ };
+
+ final private EGLConfig chooseEglConfig(EGLConfig[] configs) {
+ EGLConfig res = configs[0];
+ int bestScore = -1;
+
+ Log.d(LOG_TAG, "EGL configs:");
+
+ for (EGLConfig config : configs) {
+ EglAttribs attr = new EglAttribs(config);
+
+ // must have
+ if ((attr.get(EGL10.EGL_SURFACE_TYPE) & EGL10.EGL_WINDOW_BIT) == 0)
+ continue;
+
+ int score = attr.weight();
+
+ Log.d(LOG_TAG, String.format("%s (%d)", attr.toString(), score));
+
+ if (score > bestScore) {
+ res = config;
+ bestScore = score;
+ }
+ }
+
+ if (bestScore < 0)
+ Log.e(LOG_TAG,
+ "Unable to find an acceptable EGL config, expect badness.");
+
+ Log.d(LOG_TAG, String.format("Chosen EGL config: %s",
+ new EglAttribs(res).toString()));
+
+ return res;
+ }
+
+ static {
+ // For grabbing with gdb...
+ final boolean sleep_for_debugger = false;
+ if (sleep_for_debugger) {
+ try {
+ Thread.sleep(20 * 1000);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ File cache_dir = 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<String, Integer, Void> mUnpacker;
+ private final static int REQUEST_MARKET = 1;
+
+ // Android 3.1+ only
+ public static final int FLAG_INCLUDE_STOPPED_PACKAGES = 32;
+
+ private static class UnpackJob {
+ public ZipFile zipfile;
+ public Set<String> paths;
+
+ public UnpackJob(ZipFile zipfile, Set<String> paths) {
+ this.zipfile = zipfile;
+ this.paths = paths;
+ }
+
+ public long UnpackSize() {
+ long size = 0;
+ for (String path: paths) {
+ ZipEntry entry = zipfile.getEntry(path);
+ if (entry != null) size += entry.getSize();
+ }
+ return size;
+ }
+ }
+
+ private class UnpackTask extends AsyncTask<String, Integer, Void> {
+ @Override
+ protected void onProgressUpdate(Integer... progress) {
+ mProgress.setIndeterminate(false);
+ mProgress.setMax(progress[1]);
+ mProgress.setProgress(progress[0]);
+ mProgress.postInvalidate();
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ Bundle md = getMetaData();
+ String nextActivity = md.getString(META_NEXT_ACTIVITY);
+ if (nextActivity != null) {
+ final ComponentName cn =
+ ComponentName.unflattenFromString(nextActivity);
+ if (cn != null) {
+ final Intent origIntent = getIntent();
+ Intent intent = new Intent();
+ intent.setComponent(cn);
+ if (origIntent.getExtras() != null)
+ intent.putExtras(origIntent.getExtras());
+ intent.putExtra(Intent.EXTRA_INTENT, origIntent);
+ intent.setDataAndType(origIntent.getData(),
+ origIntent.getType());
+ //intent.fillIn(getIntent(), 0);
+ intent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
+ Log.i(LOG_TAG,
+ "Starting next activity with intent " + intent);
+ startActivity(intent);
+ } else {
+ Log.w(LOG_TAG,
+ "Unable to extract a component name from " + nextActivity);
+ }
+ }
+
+ finish();
+ }
+
+ @Override
+ protected Void doInBackground(String... all_libs) {
+ // This will contain all unpack jobs
+ Map<String, UnpackJob> unpack_jobs =
+ new HashMap<String, UnpackJob>(all_libs.length);
+
+ // This will contain all unpack filenames (so we can
+ // detect stale files in the unpack directory)
+ Set<String> all_files = new HashSet<String>(all_libs.length);
+
+ for (String lib: all_libs) {
+ final Uri uri = Uri.parse(lib);
+ final String pkg = uri.getAuthority();
+ final String path = uri.getPath().substring(1); // skip first /
+
+ all_files.add(new File(path).getName());
+
+ UnpackJob job = unpack_jobs.get(pkg);
+ if (job == null) {
+ try {
+ // getPackageResourcePath is hidden in Context,
+ // but exposed in ContextWrapper...
+ ContextWrapper context =
+ new ContextWrapper(createPackageContext(pkg, 0));
+ ZipFile zipfile =
+ new ZipFile(context.getPackageResourcePath());
+ job = new UnpackJob(zipfile, new HashSet<String>(1));
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(LOG_TAG, "Package " + pkg +
+ " not found", e);
+ continue;
+ } catch (IOException e) {
+ // FIXME: show some sort of GUI error dialog
+ Log.e(LOG_TAG,
+ "Error opening ZIP for package " + pkg, e);
+ continue;
+ }
+ unpack_jobs.put(pkg, job);
+ }
+ job.paths.add(path);
+ }
+
+ // Delete stale filenames from mUnpackDest
+ for (File file: mUnpackDest.listFiles()) {
+ if (!all_files.contains(file.getName())) {
+ Log.i(LOG_TAG,
+ "Deleting stale cached file " + file);
+ file.delete();
+ }
+ }
+
+ int total_size = 0;
+ for (UnpackJob job: unpack_jobs.values())
+ total_size += job.UnpackSize();
+
+ publishProgress(0, total_size);
+
+ mUnpackDest.mkdirs();
+
+ int progress = 0;
+
+ for (UnpackJob job: unpack_jobs.values()) {
+ try {
+ ZipFile zipfile = job.zipfile;
+ for (String path: job.paths) {
+ ZipEntry zipentry = zipfile.getEntry(path);
+ if (zipentry == null)
+ throw new FileNotFoundException(
+ "Couldn't find " + path + " in zip");
+ File dest = new File(mUnpackDest, new File(path).getName());
+ if (dest.exists() &&
+ dest.lastModified() == zipentry.getTime() &&
+ dest.length() == zipentry.getSize()) {
+ // Already unpacked
+ progress += zipentry.getSize();
+ } else {
+ if (dest.exists())
+ Log.d(LOG_TAG,
+ "Replacing " + dest.getPath() +
+ " old.mtime=" + dest.lastModified() +
+ " new.mtime=" + zipentry.getTime() +
+ " old.size=" + dest.length() +
+ " new.size=" + zipentry.getSize());
+ else
+ Log.i(LOG_TAG,
+ "Extracting " + zipentry.getName() +
+ " from " + zipfile.getName() +
+ " to " + dest.getPath());
+
+ long next_update = progress;
+
+ InputStream in = zipfile.getInputStream(zipentry);
+ OutputStream out = new FileOutputStream(dest);
+ int len;
+ byte[] buffer = new byte[4096];
+ while ((len = in.read(buffer)) != -1) {
+ out.write(buffer, 0, len);
+ progress += len;
+ if (progress >= next_update) {
+ publishProgress(progress, total_size);
+ // Arbitrary limit of 2% update steps
+ next_update += total_size / 50;
+ }
+ }
+
+ in.close();
+ out.close();
+ dest.setLastModified(zipentry.getTime());
+ }
+ publishProgress(progress, total_size);
+ }
+
+ zipfile.close();
+ } catch (IOException e) {
+ // FIXME: show some sort of GUI error dialog
+ Log.e(LOG_TAG, "Error unpacking plugin", e);
+ }
+ }
+
+ if (progress != total_size)
+ Log.d(LOG_TAG, "Ended with progress " + progress +
+ " != total size " + total_size);
+
+ setResult(RESULT_OK);
+
+ return null;
+ }
+ }
+
+ private class PluginBroadcastReciever extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!intent.getAction()
+ .equals(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<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);
+ // 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<ResolveInfo> plugins = getPackageManager()
+ .queryBroadcastReceivers(intent, 0);
+ if (PLUGINS_ENABLED && plugins.isEmpty()) {
+ // No plugins installed
+ AlertDialog.Builder alert = new AlertDialog.Builder(this)
+ .setTitle(R.string.no_plugins_title)
+ .setMessage(R.string.no_plugins_found)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setOnCancelListener(new DialogInterface.OnCancelListener() {
+ public void onCancel(DialogInterface dialog) {
+ finish();
+ }
+ })
+ .setNegativeButton(R.string.quit,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ finish();
+ }
+ });
+
+ final Uri uri = Uri.parse("market://search?q=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();
+ }
+ }
+}