From 3687aa18c00577df9810003a4a75a01f3db75e33 Mon Sep 17 00:00:00 2001 From: Joshua Granick Date: Mon, 8 Dec 2014 17:42:18 -0800 Subject: [PATCH] Initial Lime support on Android using SDL2, need to handle assets next --- .../src/org/haxe/extension/Extension.java | 43 +- legacy/project/Build.xml | 2 +- project/Build.xml | 8 +- project/lib/sdl | 2 +- project/src/backend/sdl/SDLApplication.cpp | 5 + .../android/template/AndroidManifest.xml | 6 +- .../src/org/haxe/lime/GameActivity.java | 702 +------- .../template/src/org/haxe/lime/MainView.java | 740 --------- .../template/src/org/haxe/lime/Sound.java | 415 ----- .../src/org/libsdl/app/SDLActivity.java | 1410 +++++++++++++++++ 10 files changed, 1517 insertions(+), 1816 deletions(-) delete mode 100644 templates/android/template/src/org/haxe/lime/MainView.java delete mode 100644 templates/android/template/src/org/haxe/lime/Sound.java create mode 100644 templates/android/template/src/org/libsdl/app/SDLActivity.java diff --git a/dependencies/extension-api/src/org/haxe/extension/Extension.java b/dependencies/extension-api/src/org/haxe/extension/Extension.java index 852747846..d22ea83cc 100644 --- a/dependencies/extension-api/src/org/haxe/extension/Extension.java +++ b/dependencies/extension-api/src/org/haxe/extension/Extension.java @@ -106,6 +106,28 @@ public class Extension { + } + + + /** + * Called after onStart() when the activity is being re-initialized from + * a previously saved state. + */ + public void onRestoreInstanceState (Bundle savedState) { + + + + } + + + /** + * Called to retrieve per-instance state from an activity before being + * killed so that the state can be restored in onCreate + */ + public void onSaveInstanceState (Bundle outState) { + + + } @@ -143,23 +165,6 @@ public class Extension { } - - - /** - * Called to retrieve per-instance state from an activity before being - * killed so that the state can be restored in onCreate - */ - public void onSaveInstanceState (Bundle outState) { - - } - - /** - * Called after onStart() when the activity is being re-initialized from - * a previously saved state. - */ - public void onRestoreInstanceState (Bundle savedState) { - - } - -} + +} \ No newline at end of file diff --git a/legacy/project/Build.xml b/legacy/project/Build.xml index 08b5328a9..87286086d 100644 --- a/legacy/project/Build.xml +++ b/legacy/project/Build.xml @@ -377,6 +377,7 @@ + @@ -388,7 +389,6 @@ - diff --git a/project/Build.xml b/project/Build.xml index 4e777d2aa..81080ecb6 100644 --- a/project/Build.xml +++ b/project/Build.xml @@ -26,6 +26,7 @@ + @@ -90,7 +91,7 @@ -
+
@@ -223,6 +224,7 @@ + @@ -239,7 +241,9 @@
- + + + diff --git a/project/lib/sdl b/project/lib/sdl index 2c58bd673..47e23f265 160000 --- a/project/lib/sdl +++ b/project/lib/sdl @@ -1 +1 @@ -Subproject commit 2c58bd673bb5e0286b2a25cc6245a4cc55d87fa4 +Subproject commit 47e23f2659d729c16af96b91f6fe733e6f917ca0 diff --git a/project/src/backend/sdl/SDLApplication.cpp b/project/src/backend/sdl/SDLApplication.cpp index 32d724f3f..1c8852c89 100644 --- a/project/src/backend/sdl/SDLApplication.cpp +++ b/project/src/backend/sdl/SDLApplication.cpp @@ -353,3 +353,8 @@ namespace lime { } + + +#ifdef ANDROID +int SDL_main (int argc, char *argv[]) { return 0; } +#endif \ No newline at end of file diff --git a/templates/android/template/AndroidManifest.xml b/templates/android/template/AndroidManifest.xml index d58c973a8..3cc8be5fb 100644 --- a/templates/android/template/AndroidManifest.xml +++ b/templates/android/template/AndroidManifest.xml @@ -1,15 +1,15 @@ - ::if WIN_REQUIRE_SHADERS::::elseif WIN_ALLOW_SHADERS::::end:: + ::if (ANDROID_PERMISSIONS != null)::::foreach ANDROID_PERMISSIONS:: ::end::::end:: - + - + diff --git a/templates/android/template/src/org/haxe/lime/GameActivity.java b/templates/android/template/src/org/haxe/lime/GameActivity.java index 54dd8e142..07ab55297 100644 --- a/templates/android/template/src/org/haxe/lime/GameActivity.java +++ b/templates/android/template/src/org/haxe/lime/GameActivity.java @@ -1,401 +1,27 @@ package org.haxe.lime; -import android.app.Activity; -import android.content.res.AssetFileDescriptor; import android.content.res.AssetManager; -import android.content.res.Configuration; -import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; -import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.hardware.SensorEventListener; -import android.hardware.SensorManager; -import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.os.Environment; import android.os.Handler; -import android.os.Vibrator; -import android.util.DisplayMetrics; -import android.util.Log; -import android.view.inputmethod.InputMethodManager; import android.view.View; -import android.view.ViewGroup; -import android.view.Window; -import android.view.WindowManager; -import dalvik.system.DexClassLoader; -import java.io.File; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.lang.reflect.Constructor; -import java.lang.Math; -import java.lang.Runnable; import java.util.ArrayList; -import java.util.HashMap; -import java.util.Locale; import java.util.List; + import org.haxe.extension.Extension; import org.haxe.HXCPP; +import org.libsdl.app.SDLActivity; -public class GameActivity extends Activity implements SensorEventListener { +public class GameActivity extends SDLActivity { - private static final int DEVICE_ORIENTATION_UNKNOWN = 0; - private static final int DEVICE_ORIENTATION_PORTRAIT = 1; - private static final int DEVICE_ORIENTATION_PORTRAIT_UPSIDE_DOWN = 2; - private static final int DEVICE_ORIENTATION_LANDSCAPE_RIGHT = 3; - private static final int DEVICE_ORIENTATION_LANDSCAPE_LEFT = 4; - private static final int DEVICE_ORIENTATION_FACE_UP = 5; - private static final int DEVICE_ORIENTATION_FACE_DOWN = 6; - private static final int DEVICE_ROTATION_0 = 0; - private static final int DEVICE_ROTATION_90 = 1; - private static final int DEVICE_ROTATION_180 = 2; - private static final int DEVICE_ROTATION_270 = 3; - private static final String GLOBAL_PREF_FILE = "nmeAppPrefs"; - - private static float[] accelData = new float[3]; - private static GameActivity activity; - private static AssetManager mAssets; - private static int bufferedDisplayOrientation = -1; - private static int bufferedNormalOrientation = -1; - private static Context mContext; + private static AssetManager assetManager; private static List extensions; - private static float[] inclinationMatrix = new float[16]; - private static HashMap mLoadedClasses = new HashMap(); - private static float[] magnetData = new float[3]; - private static DisplayMetrics metrics; - private static float[] orientData = new float[3]; - private static float[] rotationMatrix = new float[16]; - private static SensorManager sensorManager; - public Handler mHandler; - - private static MainView mMainView; - private MainView mView; - private Sound _sound; - - - protected void onCreate (Bundle state) { - - super.onCreate (state); - - activity = this; - mContext = this; - mHandler = new Handler (); - mAssets = getAssets (); - - Extension.assetManager = mAssets; - Extension.callbackHandler = mHandler; - Extension.mainActivity = this; - Extension.mainContext = this; - - _sound = new Sound (getApplication ()); - - requestWindowFeature (Window.FEATURE_NO_TITLE); - - ::if WIN_FULLSCREEN:: - ::if (ANDROID_TARGET_SDK_VERSION < 19):: - getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN - | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - ::end:: - ::end:: - - metrics = new DisplayMetrics (); - getWindowManager ().getDefaultDisplay ().getMetrics (metrics); - - ::foreach ndlls:: - System.loadLibrary ("::name::"); - ::end:: - HXCPP.run ("ApplicationMain"); - - mView = new MainView (getApplication (), this); - setContentView (mView); - - Extension.mainView = mView; - - sensorManager = (SensorManager)activity.getSystemService (Context.SENSOR_SERVICE); - - if (sensorManager != null) { - - sensorManager.registerListener (this, sensorManager.getDefaultSensor (Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_GAME); - sensorManager.registerListener (this, sensorManager.getDefaultSensor (Sensor.TYPE_MAGNETIC_FIELD), SensorManager.SENSOR_DELAY_GAME); - - } - - Extension.packageName = getApplicationContext ().getPackageName (); - - if (extensions == null) { - - extensions = new ArrayList (); - ::if (ANDROID_EXTENSIONS != null)::::foreach ANDROID_EXTENSIONS:: - extensions.add (new ::__current__:: ());::end::::end:: - - } - - for (Extension extension : extensions) { - - extension.onCreate (state); - - } - - } - - // IMMERSIVE MODE SUPPORT - ::if (WIN_FULLSCREEN)::::if (ANDROID_TARGET_SDK_VERSION >= 19):: - - @Override - public void onWindowFocusChanged(boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); - - if(hasFocus) { - hideSystemUi(); - } - } - - private void hideSystemUi() { - View decorView = this.getWindow().getDecorView(); - - decorView.setSystemUiVisibility( - View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_FULLSCREEN - | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); - } - - ::end::::end:: - - public static double CapabilitiesGetPixelAspectRatio () { - - return metrics.xdpi / metrics.ydpi; - - } - - - public static double CapabilitiesGetScreenDPI () { - - return metrics.xdpi; - - } - - - public static double CapabilitiesGetScreenResolutionX () { - - return metrics.widthPixels; - - } - - - public static double CapabilitiesGetScreenResolutionY () { - - return metrics.heightPixels; - - } - - - public static String CapabilitiesGetLanguage () { - - return Locale.getDefault ().getLanguage (); - - } - - - public static void clearUserPreference (String inId) { - - SharedPreferences prefs = activity.getSharedPreferences (GLOBAL_PREF_FILE, MODE_PRIVATE); - SharedPreferences.Editor prefEditor = prefs.edit (); - prefEditor.putString (inId, ""); - prefEditor.commit (); - - } - - - public void doPause () { - - _sound.doPause (); - - mView.sendActivity (Lime.DEACTIVATE); - mView.onPause (); - - if (sensorManager != null) { - - sensorManager.unregisterListener (this); - - } - - } - - - public void doResume () { - - mView.onResume (); - - _sound.doResume (); - - mView.sendActivity (Lime.ACTIVATE); - - if (sensorManager != null) { - - sensorManager.registerListener (this, sensorManager.getDefaultSensor (Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_GAME); - sensorManager.registerListener (this, sensorManager.getDefaultSensor (Sensor.TYPE_MAGNETIC_FIELD), SensorManager.SENSOR_DELAY_GAME); - - } - - } - - - public static AssetManager getAssetManager () { - - return mAssets; - - } - - - public static Context getContext () { - - return mContext; - - } - - - public static GameActivity getInstance () { - - return activity; - - } - - - public static MainView getMainView () { - - return activity.mView; - - } - - - public static byte[] getResource (String inResource) { - - try { - - InputStream inputStream = mAssets.open (inResource, AssetManager.ACCESS_BUFFER); - long length = inputStream.available (); - byte[] result = new byte[(int)length]; - inputStream.read (result); - inputStream.close (); - return result; - - } catch (IOException e) { - - Log.e ("GameActivity", "getResource" + ":" + e.toString ()); - - } - - return null; - - } - - - public static int getResourceID (String inFilename) { - - //::foreach assets::::if (type == "music")::if (inFilename.equals("::id::")) return ::APP_PACKAGE::.R.raw.::flatName::; - //::end::::end:: - //::foreach assets::::if (type == "sound")::if (inFilename.equals("::id::")) return ::APP_PACKAGE::.R.raw.::flatName::; - //::end::::end:: - return -1; - - } - - - static public String getSpecialDir (int inWhich) { - - //Log.v ("GameActivity", "Get special Dir " + inWhich); - File path = null; - - switch (inWhich) { - - case 0: // App - return mContext.getPackageCodePath (); - - case 1: // Storage - path = mContext.getFilesDir (); - break; - - case 2: // Desktop - path = Environment.getDataDirectory (); - break; - - case 3: // Docs - path = Environment.getExternalStorageDirectory (); - break; - - case 4: // User - path = mContext.getExternalFilesDir (Environment.DIRECTORY_DOWNLOADS); - break; - - } - - return path == null ? "" : path.getAbsolutePath (); - - } - - - public static String getUserPreference (String inId) { - - SharedPreferences prefs = activity.getSharedPreferences (GLOBAL_PREF_FILE, MODE_PRIVATE); - return prefs.getString (inId, ""); - - } - - - public static void launchBrowser (String inURL) { - - Intent browserIntent = new Intent (Intent.ACTION_VIEW).setData (Uri.parse (inURL)); - - try { - - activity.startActivity (browserIntent); - - } catch (Exception e) { - - Log.e ("GameActivity", e.toString ()); - return; - - } - - } - - - private void loadNewSensorData (SensorEvent event) { - - final int type = event.sensor.getType (); - - if (type == Sensor.TYPE_ACCELEROMETER) { - - accelData = event.values.clone (); - Lime.onAccelerate (-accelData[0], -accelData[1], accelData[2]); - - } - - if (type == Sensor.TYPE_MAGNETIC_FIELD) { - - magnetData = event.values.clone (); - //Log.d("GameActivity","new mag: " + magnetData[0] + ", " + magnetData[1] + ", " + magnetData[2]); - - } - - } - - - @Override public void onAccuracyChanged (Sensor sensor, int accuracy) { - - - - } + public Handler handler; @Override protected void onActivityResult (int requestCode, int resultCode, Intent data) { @@ -415,6 +41,37 @@ public class GameActivity extends Activity implements SensorEventListener { } + protected void onCreate (Bundle state) { + + super.onCreate (state); + + assetManager = getAssets (); + handler = new Handler (); + + Extension.assetManager = assetManager; + Extension.callbackHandler = handler; + Extension.mainActivity = this; + Extension.mainContext = this; + Extension.mainView = mLayout; + Extension.packageName = getApplicationContext ().getPackageName (); + + if (extensions == null) { + + extensions = new ArrayList (); + ::if (ANDROID_EXTENSIONS != null)::::foreach ANDROID_EXTENSIONS:: + extensions.add (new ::__current__:: ());::end::::end:: + + } + + for (Extension extension : extensions) { + + extension.onCreate (state); + + } + + } + + @Override protected void onDestroy () { for (Extension extension : extensions) { @@ -423,9 +80,6 @@ public class GameActivity extends Activity implements SensorEventListener { } - // TODO: Wait for result? - mView.sendActivity (Lime.DESTROY); - activity = null; super.onDestroy (); } @@ -459,7 +113,6 @@ public class GameActivity extends Activity implements SensorEventListener { @Override protected void onPause () { - doPause (); super.onPause (); for (Extension extension : extensions) { @@ -487,7 +140,6 @@ public class GameActivity extends Activity implements SensorEventListener { @Override protected void onResume () { super.onResume(); - doResume(); for (Extension extension : extensions) { @@ -496,59 +148,48 @@ public class GameActivity extends Activity implements SensorEventListener { } } - + + + @Override protected void onRestoreInstanceState (Bundle savedState) { + + super.onRestoreInstanceState (savedState); + + for (Extension extension : extensions) { + + extension.onRestoreInstanceState (savedState); + + } + + } + @Override protected void onSaveInstanceState (Bundle outState) { - super.onSaveInstanceState(outState); + + super.onSaveInstanceState (outState); + for (Extension extension : extensions) { + extension.onSaveInstanceState (outState); - } - } - - @Override protected void onRestoreInstanceState (Bundle savedState) { - super.onRestoreInstanceState(savedState); - for (Extension extension : extensions) { - extension.onRestoreInstanceState (savedState); - } - } - - - @Override public void onSensorChanged (SensorEvent event) { - - loadNewSensorData (event); - - if (accelData != null && magnetData != null) { - - boolean foundRotationMatrix = SensorManager.getRotationMatrix (rotationMatrix, inclinationMatrix, accelData, magnetData); - - if (foundRotationMatrix) { - - SensorManager.getOrientation (rotationMatrix, orientData); - Lime.onOrientationUpdate (orientData[0], orientData[1], orientData[2]); - - } } - Lime.onDeviceOrientationUpdate (prepareDeviceOrientation ()); - Lime.onNormalOrientationFound (bufferedNormalOrientation); - } @Override protected void onStart () { - super.onStart(); + super.onStart (); - ::if WIN_FULLSCREEN::::if (ANDROID_TARGET_SDK_VERSION >= 16):: - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + ::if WIN_FULLSCREEN::::if (ANDROID_TARGET_SDK_VERSION < 19):: + if (Build.VERSION.SDK_INT >= 19) { - getWindow().getDecorView().setSystemUiVisibility (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_FLAG_FULLSCREEN); + getWindow ().getDecorView ().setSystemUiVisibility (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_FLAG_FULLSCREEN); } ::end::::end:: for (Extension extension : extensions) { + extension.onStart (); } @@ -584,232 +225,23 @@ public class GameActivity extends Activity implements SensorEventListener { ::end:: - public static void popView () { + @Override public void onWindowFocusChanged(boolean hasFocus) { - activity.setContentView (activity.mView); - activity.doResume (); + super.onWindowFocusChanged (hasFocus); - } - - - public static void postUICallback (final long inHandle) { - - activity.mHandler.post (new Runnable () { + ::if WIN_FULLSCREEN::::if (ANDROID_TARGET_SDK_VERSION >= 19):: + if (hasFocus) { - @Override public void run () { + if (Build.VERSION.SDK_INT >= 19) { - Lime.onCallback (inHandle); - - } - - }); - - } - - - private int prepareDeviceOrientation () { - - int rawOrientation = getWindow ().getWindowManager ().getDefaultDisplay ().getOrientation (); - - if (rawOrientation != bufferedDisplayOrientation) { - - bufferedDisplayOrientation = rawOrientation; - - } - - int screenOrientation = getResources ().getConfiguration ().orientation; - int deviceOrientation = DEVICE_ORIENTATION_UNKNOWN; - - if (bufferedNormalOrientation < 0) { - - switch (screenOrientation) { - - case Configuration.ORIENTATION_LANDSCAPE: - - switch (bufferedDisplayOrientation) { - - case DEVICE_ROTATION_0: - case DEVICE_ROTATION_180: - bufferedNormalOrientation = DEVICE_ORIENTATION_LANDSCAPE_LEFT; - break; - - case DEVICE_ROTATION_90: - case DEVICE_ROTATION_270: - bufferedNormalOrientation = DEVICE_ORIENTATION_PORTRAIT; - break; - - default: - bufferedNormalOrientation = DEVICE_ORIENTATION_UNKNOWN; - - } - - break; - - case Configuration.ORIENTATION_PORTRAIT: - - switch (bufferedDisplayOrientation) { - - case DEVICE_ROTATION_0: - case DEVICE_ROTATION_180: - bufferedNormalOrientation = DEVICE_ORIENTATION_PORTRAIT; - break; - - case DEVICE_ROTATION_90: - case DEVICE_ROTATION_270: - bufferedNormalOrientation = DEVICE_ORIENTATION_LANDSCAPE_LEFT; - break; - - default: - bufferedNormalOrientation = DEVICE_ORIENTATION_UNKNOWN; - - } - - break; - - default: // ORIENTATION_SQUARE OR ORIENTATION_UNDEFINED - bufferedNormalOrientation = DEVICE_ORIENTATION_UNKNOWN; + getWindow ().getDecorView ().setSystemUiVisibility (View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); } } - - switch (screenOrientation) { - - case Configuration.ORIENTATION_LANDSCAPE: - - switch (bufferedDisplayOrientation) { - - case DEVICE_ROTATION_0: - case DEVICE_ROTATION_270: - deviceOrientation = DEVICE_ORIENTATION_LANDSCAPE_LEFT; - break; - - case DEVICE_ROTATION_90: - case DEVICE_ROTATION_180: - deviceOrientation = DEVICE_ORIENTATION_LANDSCAPE_RIGHT; - break; - - default: // impossible! - deviceOrientation = DEVICE_ORIENTATION_UNKNOWN; - - } - - break; - - case Configuration.ORIENTATION_PORTRAIT: - - switch (bufferedDisplayOrientation) { - - case DEVICE_ROTATION_0: - case DEVICE_ROTATION_90: - deviceOrientation = DEVICE_ORIENTATION_PORTRAIT; - break; - - case DEVICE_ROTATION_180: - case DEVICE_ROTATION_270: - deviceOrientation = DEVICE_ORIENTATION_PORTRAIT_UPSIDE_DOWN; - break; - - default: // impossible! - deviceOrientation = DEVICE_ORIENTATION_UNKNOWN; - } - - break; - - default: // ORIENTATION_SQUARE OR ORIENTATION_UNDEFINED - deviceOrientation = DEVICE_ORIENTATION_UNKNOWN; - - } - - return deviceOrientation; + ::end::::end:: } - public static void pushView (View inView) { - - activity.doPause (); - activity.setContentView (inView); - - } - - - public void queueRunnable (Runnable runnable) { - - Log.e ("GameActivity", "queueing..."); - - } - - - public static void registerExtension (Extension extension) { - - if (extensions.indexOf (extension) == -1) { - - extensions.add (extension); - - } - - } - - - public static void showKeyboard (boolean show) { - - if (activity == null) { - - return; - - } - - InputMethodManager mgr = (InputMethodManager)activity.getSystemService (Context.INPUT_METHOD_SERVICE); - mgr.hideSoftInputFromWindow (activity.mView.getWindowToken (), 0); - - if (show) { - - mgr.toggleSoftInput (InputMethodManager.SHOW_FORCED, 0); - // On the Nexus One, SHOW_FORCED makes it impossible - // to manually dismiss the keyboard. - // On the Droid SHOW_IMPLICIT doesn't bring up the keyboard. - - } - - } - - - public static void setUserPreference (String inId, String inPreference) { - - SharedPreferences prefs = activity.getSharedPreferences (GLOBAL_PREF_FILE, MODE_PRIVATE); - SharedPreferences.Editor prefEditor = prefs.edit (); - prefEditor.putString (inId, inPreference); - prefEditor.commit (); - - } - - - public static void vibrate (int period, int duration) { - - Vibrator v = (Vibrator)activity.getSystemService (Context.VIBRATOR_SERVICE); - - if (period == 0) { - - v.vibrate (duration); - - } else { - - int periodMS = (int)Math.ceil (period / 2); - int count = (int)Math.ceil ((duration / period) * 2); - long[] pattern = new long[count]; - - for (int i = 0; i < count; i++) { - - pattern[i] = periodMS; - - } - - v.vibrate (pattern, -1); - - } - - } - - -} +} \ No newline at end of file diff --git a/templates/android/template/src/org/haxe/lime/MainView.java b/templates/android/template/src/org/haxe/lime/MainView.java deleted file mode 100644 index 7db41c224..000000000 --- a/templates/android/template/src/org/haxe/lime/MainView.java +++ /dev/null @@ -1,740 +0,0 @@ -package org.haxe.lime; - - -import android.app.Activity; -import android.content.Context; -import android.graphics.PixelFormat; -import android.opengl.GLSurfaceView; -import android.os.Build; -import android.os.SystemClock; -import android.text.InputType; -import android.util.AttributeSet; -import android.util.Log; -import android.view.inputmethod.BaseInputConnection; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputConnection; -::if (ANDROID_TARGET_SDK_VERSION > 11)::import android.view.InputDevice;::end:: -import android.view.KeyCharacterMap; -import android.view.KeyEvent; -import android.view.MotionEvent; -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.opengles.GL10; -import java.util.Timer; -import java.util.TimerTask; - - -class MainView extends GLSurfaceView { - - - static final int etTouchBegin = 15; - static final int etTouchMove = 16; - static final int etTouchEnd = 17; - static final int etTouchTap = 18; - static final int resTerminate = -1; - - boolean isPollImminent; - Activity mActivity; - static MainView mRefreshView; - Timer mTimer = new Timer (); - int mTimerID = 0; - TimerTask pendingTimer; - Runnable pollMe; - boolean renderPending = false; - - - public MainView (Context context, Activity inActivity) { - - super (context); - - isPollImminent = false; - final MainView me = this; - - pollMe = new Runnable () { - - @Override public void run () { me.onPoll (); } - - }; - - int eglVersion = 1; - - if (::WIN_ALLOW_SHADERS:: || ::WIN_REQUIRE_SHADERS::) { - - EGL10 egl = (EGL10)EGLContext.getEGL (); - EGLDisplay display = egl.eglGetDisplay (EGL10.EGL_DEFAULT_DISPLAY); - int[] version = new int[2]; - - egl.eglInitialize (display, version); - - EGLConfig[] v2_configs = new EGLConfig[1]; - int[] num_config = new int[1]; - int[] attrs = { EGL10.EGL_RENDERABLE_TYPE, ::if DEFINE_LIME_FORCE_GLES1::1::else::4::end:: /*EGL_OPENGL_ES2_BIT*/, EGL10.EGL_NONE }; - egl.eglChooseConfig (display, attrs, v2_configs, 1, num_config); - - if (num_config[0]==1) { - - eglVersion = ::if DEFINE_LIME_FORCE_GLES1::1::else::2::end::; - setEGLContextClientVersion (::if DEFINE_LIME_FORCE_GLES1::1::else::2::end::); - - } - - } - - final int renderType = (eglVersion == 1 ? 0x01 : 0x04); - - setEGLConfigChooser (new EGLConfigChooser () { - - public EGLConfig chooseConfig (EGL10 egl, EGLDisplay display) { - - int depth = ::if WIN_DEPTH_BUFFER::16::else::0::end::; - int stencil = ::if WIN_STENCIL_BUFFER::8::else::0::end::; - EGLConfig[] configs = new EGLConfig[1]; - int[] num_config = new int[1]; - - if (::WIN_ANTIALIASING:: > 1) { - - int[] attrs = { - - EGL10.EGL_DEPTH_SIZE, depth, - EGL10.EGL_STENCIL_SIZE, stencil, - EGL10.EGL_SAMPLE_BUFFERS, 1 /* true */, - EGL10.EGL_SAMPLES, ::WIN_ANTIALIASING::, - EGL10.EGL_RENDERABLE_TYPE, renderType, - EGL10.EGL_NONE - - }; - - egl.eglChooseConfig (display, attrs, configs, 1, num_config); - - if (num_config[0] == 1) { - - return configs[0]; - - } - - if (::WIN_ANTIALIASING:: > 2) { - - int[] attrs_aa2 = { - - EGL10.EGL_DEPTH_SIZE, depth, - EGL10.EGL_STENCIL_SIZE, stencil, - EGL10.EGL_SAMPLE_BUFFERS, 1 /* true */, - EGL10.EGL_SAMPLES, 2, - EGL10.EGL_RENDERABLE_TYPE, renderType, - EGL10.EGL_NONE - - }; - - egl.eglChooseConfig (display, attrs_aa2, configs, 1, num_config); - - if (num_config[0] == 1) { - - return configs[0]; - - } - - } - - final int EGL_COVERAGE_BUFFERS_NV = 0x30E0; - final int EGL_COVERAGE_SAMPLES_NV = 0x30E1; - - int[] attrs_aanv = { - - EGL10.EGL_DEPTH_SIZE, depth, - EGL10.EGL_STENCIL_SIZE, stencil, - EGL_COVERAGE_BUFFERS_NV, 1 /* true */, - EGL_COVERAGE_SAMPLES_NV, 2, // always 5 in practice on tegra 2 - EGL10.EGL_RENDERABLE_TYPE, renderType, - EGL10.EGL_NONE - - }; - - egl.eglChooseConfig (display, attrs_aanv, configs, 1, num_config); - - if (num_config[0] == 1) { - - return configs[0]; - - } - - } - - int[] attrs1 = { - - EGL10.EGL_DEPTH_SIZE, depth, - EGL10.EGL_STENCIL_SIZE, stencil, - EGL10.EGL_RENDERABLE_TYPE, renderType, - EGL10.EGL_NONE - - }; - - egl.eglChooseConfig (display, attrs1, configs, 1, num_config); - - if (num_config[0] == 1) { - - return configs[0]; - - } - - int[] attrs2 = { - - EGL10.EGL_NONE - - }; - - egl.eglChooseConfig (display, attrs2, configs, 1, num_config); - - if (num_config[0] == 1) { - - return configs[0]; - - } - - return null; - - } - - }); - - mActivity = inActivity; - mRefreshView = this; - setFocusable (true); - setFocusableInTouchMode (true); - setRenderer (new Renderer (this)); - setRenderMode (GLSurfaceView.RENDERMODE_WHEN_DIRTY); - - } - - - // Haxe Thread - public void HandleResult (int inCode) { - - if (inCode == resTerminate) { - - mActivity.finish (); - return; - - } - - double wake = Lime.getNextWake (); - int delayMS = (int)(wake * 1000); - - if (renderPending && delayMS < 5) { - - delayMS = 5; - - } - - if (delayMS <= 1) { - - queuePoll (); - - } else { - - if (pendingTimer != null) { - - pendingTimer.cancel (); - - } - - final MainView me = this; - pendingTimer = new TimerTask () { - - @Override public void run () { - - me.queuePoll (); - - } - - }; - - mTimer.schedule (pendingTimer, delayMS); - - } - - } - - - @Override public InputConnection onCreateInputConnection (EditorInfo outAttrs) { - - BaseInputConnection inputConnection = new BaseInputConnection (this, false) { - - @Override public boolean deleteSurroundingText (int beforeLength, int afterLength) { - - ::if (ANDROID_TARGET_SDK_VERSION > 15):: - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - - if (beforeLength == 1 && afterLength == 0) { - - final long time = SystemClock.uptimeMillis (); - - super.sendKeyEvent (new KeyEvent (time, time, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE)); - super.sendKeyEvent (new KeyEvent (SystemClock.uptimeMillis(), time, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE)); - - return true; - - } - - } - ::end:: - - return super.deleteSurroundingText (beforeLength, afterLength); - - } - - }; - - return inputConnection; - - } - - - ::if (ANDROID_TARGET_SDK_VERSION > 11)::@Override public boolean onGenericMotionEvent (MotionEvent event) { - - if ((event.getSource () & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 && event.getAction () == MotionEvent.ACTION_MOVE) { - - final MainView me = this; - final InputDevice device = event.getDevice (); - final int deviceId = event.getDeviceId (); - - int[] axisList = { - - android.view.MotionEvent.AXIS_X, android.view.MotionEvent.AXIS_Y, android.view.MotionEvent.AXIS_Z, - android.view.MotionEvent.AXIS_RX, android.view.MotionEvent.AXIS_RY, android.view.MotionEvent.AXIS_RZ, - android.view.MotionEvent.AXIS_HAT_X, android.view.MotionEvent.AXIS_HAT_Y, - android.view.MotionEvent.AXIS_LTRIGGER, android.view.MotionEvent.AXIS_RTRIGGER - - }; - - for (int i = 0; i < axisList.length; i++) { - - final int axis = axisList[i]; - final InputDevice.MotionRange range = device.getMotionRange (axis, event.getSource ()); - - if (range != null) { - - final float value = event.getAxisValue (axis); - - queueEvent (new Runnable () { - - public void run () { - - me.HandleResult (Lime.onJoyMotion (deviceId, axis, ((value - range.getMin ()) / (range.getRange ())) * 65535 - 32768)); - - } - - }); - - } - - } - - return true; - - } - - return super.onGenericMotionEvent (event); - - }::end:: - - - @Override public boolean onKeyDown (final int inKeyCode, KeyEvent event) { - - final MainView me = this; - - ::if (ANDROID_TARGET_SDK_VERSION > 11)::if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1 && (event.isGamepadButton (inKeyCode) || (inKeyCode >= 19 && inKeyCode <=22))) { - - if (event.getRepeatCount () == 0) { - - final int deviceId = event.getDeviceId (); - - queueEvent (new Runnable () { - - public void run () { - - me.HandleResult (Lime.onJoyChange (deviceId, inKeyCode, true)); - - } - - }); - - } - - if (inKeyCode < 19 || inKeyCode > 22) { - - return true; - - } - - }::end:: - - final int keyCode = translateKeyCode (inKeyCode, event); - final int charCode = translateCharCode (inKeyCode, event); - - if (keyCode != 0) { - - queueEvent (new Runnable () { - - public void run () { - - me.HandleResult (Lime.onKeyChange (keyCode, charCode, true)); - - } - - }); - - return true; - - } - - return super.onKeyDown (inKeyCode, event); - - } - - - @Override public boolean onKeyUp (final int inKeyCode, KeyEvent event) { - - final MainView me = this; - - ::if (ANDROID_TARGET_SDK_VERSION > 11)::if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1 && (event.isGamepadButton (inKeyCode) || (inKeyCode >= 19 && inKeyCode <=22))) { - - if (event.getRepeatCount () == 0) { - - final int deviceId = event.getDeviceId (); - - queueEvent (new Runnable () { - - public void run () { - - me.HandleResult (Lime.onJoyChange (deviceId, inKeyCode, false)); - - } - - }); - - } - - if (inKeyCode < 19 || inKeyCode > 22) { - - return true; - - } - - }::end:: - - final int keyCode = translateKeyCode (inKeyCode, event); - final int charCode = translateCharCode (inKeyCode, event); - - if (keyCode != 0) { - - queueEvent (new Runnable () { - - public void run () { - - me.HandleResult (Lime.onKeyChange (keyCode, charCode, false)); - - } - - }); - - return true; - - } - - return super.onKeyUp (inKeyCode, event); - - } - - - // Haxe Thread - void onPoll () { - - isPollImminent = false; - HandleResult (Lime.onPoll ()); - - } - - - @Override public boolean onTouchEvent (final MotionEvent ev) { - - final MainView me = this; - final int action = ev.getAction (); - int type = -1; - - switch (action & MotionEvent.ACTION_MASK) { - - case MotionEvent.ACTION_DOWN: type = etTouchBegin; break; - case MotionEvent.ACTION_POINTER_DOWN: type = etTouchBegin; break; - case MotionEvent.ACTION_MOVE: type = etTouchMove; break; - case MotionEvent.ACTION_UP: type = etTouchEnd; break; - case MotionEvent.ACTION_POINTER_UP: type = etTouchEnd; break; - case MotionEvent.ACTION_CANCEL: type = etTouchEnd; break; - - } - - int idx = (action & MotionEvent.ACTION_POINTER_ID_MASK) >> (MotionEvent.ACTION_POINTER_ID_SHIFT); - final int t = type; - - for (int i = 0; i < ev.getPointerCount (); i++) { - - final int id = ev.getPointerId (i); - final float x = ev.getX (i); - final float y = ev.getY (i); - final float sizeX = ev.getSize (i); - final float sizeY = ev.getSize (i); - - if (type == etTouchMove || i == idx) { - - queueEvent (new Runnable () { - - public void run () { - - me.HandleResult (Lime.onTouch (t, x, y, id, sizeX, sizeY)); - - } - - }); - - } - - } - - return true; - - } - - - @Override public boolean onTrackballEvent (final MotionEvent ev) { - - final MainView me = this; - - queueEvent (new Runnable () { - - public void run() { - - float x = ev.getX (); - float y = ev.getY (); - - me.HandleResult (Lime.onTrackball (x, y)); - - } - - }); - - return false; - - } - - - // GUI/Timer Thread - void queuePoll () { - - if (!isPollImminent) { - - isPollImminent = true; - queueEvent (pollMe); - - } - - } - - - - static public void renderNow () { //Called directly from C++ - - mRefreshView.renderPending = true; - mRefreshView.requestRender (); - - } - - - void sendActivity (final int inActivity) { - - queueEvent (new Runnable () { - - public void run () { - - Lime.onActivity (inActivity); - - } - - }); - - } - - - public int translateCharCode (int inCode, KeyEvent event) { - - int result = event.getUnicodeChar (event.getMetaState ()); - - if (result == KeyCharacterMap.COMBINING_ACCENT) { - - //TODO - return 0; - - } - - switch (inCode) { - - case 66: - case 160: return 13; // enter - case 111: return 27; // escape - case 67: return 8; // backspace - case 61: return 9; // tab - case 62: return 32; // space - case 112: return 127; // delete - - } - - return result; - - } - - - public int translateKeyCode (int inCode, KeyEvent event) { - - switch (inCode) { - - case KeyEvent.KEYCODE_DPAD_CENTER: return 13; // Enter - case KeyEvent.KEYCODE_BACK: return 27; /* Fake Escape */ - case KeyEvent.KEYCODE_MENU: return 0x01000012; /* Fake MENU */ - case KeyEvent.KEYCODE_DEL: return 8; - - // These will be ignored by the app and passed to the default handler - case KeyEvent.KEYCODE_VOLUME_UP: - case KeyEvent.KEYCODE_VOLUME_DOWN: - ::if (ANDROID_TARGET_SDK_VERSION > 10)::case KeyEvent.KEYCODE_VOLUME_MUTE:::end:: - return 0; - - } - - - if (inCode >= 7 && inCode <= 16) { - - return inCode + 41; // 1-9 - - } else if (inCode >= 29 && inCode <= 54) { - - return inCode + 36; // a-z - - } else if (inCode >= 131 && inCode <= 142) { - - return inCode - 19; // F1-F12 - - } else if (inCode >= 144 && inCode <= 153) { - - return inCode - 96; // 1-9 - - } - - switch (inCode) { - - case 66: - case 160: return 13; // enter - case 111: return 27; // escape - case 67: return 8; // backspace - case 61: return 9; // tab - case 62: return 32; // space - case 69: - case 156: return 189; // -_ - case 70: - case 161: return 187; // += - case 71: return 219; // [{ - case 72: return 221; // ]} - case 73: return 220; // \| - case 74: return 186; // ;: - case 75: return 222; // '" - case 68: return 192; // `~ - case 55: - case 159: return 188; // ,< - case 56: - case 158: return 190; // .> - case 76: return 191; // /? - case 115: return 20; // caps lock - case 116: return 145; // scroll lock - case 121: return 19; // pause/break - case 124: return 45; // insert - case 122: return 36; // home - case 92: return 34; // page down - case 93: return 33; // page up - case 112: return 46; // delete - case 123: return 35; // end - case 22: return 39; // right arrow - case 21: return 37; // left arrow - case 20: return 40; // down arrow - case 19: return 38; // up arrow - case 143: return 144; // num lock - case 113: - case 114: return 17; // ctrl - case 59: - case 60: return 16; // shift - case 57: - case 58: return 18; // alt - - } - - return inCode; - - } - - - - - private static class Renderer implements GLSurfaceView.Renderer { - - - MainView mMainView; - - - public Renderer (MainView inView) { - - mMainView = inView; - - } - - - public void onDrawFrame (GL10 gl) { - - mMainView.renderPending = false; - mMainView.HandleResult (Lime.onRender ()); - Sound.checkSoundCompletion (); - - } - - - public void onSurfaceChanged (GL10 gl, int width, int height) { - - ::if (DEBUG):: - Log.v("VIEW","onSurfaceChanged " + width +"," + height); - Log.v("VIEW", "Thread = " + java.lang.Thread.currentThread ().getId ()); - ::end:: - - mMainView.HandleResult (Lime.onResize (width, height)); - - /*if (GameActivity.activity != null) { - - GameActivity.activity.onResizeAsync(width,height); - - }*/ - - } - - - public void onSurfaceCreated (GL10 gl, EGLConfig config) { - - mMainView.isPollImminent = false; - mMainView.renderPending = false; - ::if (DEBUG):: - Log.v("VIEW","onSurfaceCreated"); - Log.v("VIEW", "Thread = " + java.lang.Thread.currentThread ().getId ()); - ::end:: - mMainView.HandleResult (Lime.onContextLost ()); - - } - - } - - -} diff --git a/templates/android/template/src/org/haxe/lime/Sound.java b/templates/android/template/src/org/haxe/lime/Sound.java deleted file mode 100644 index 8b8135fc1..000000000 --- a/templates/android/template/src/org/haxe/lime/Sound.java +++ /dev/null @@ -1,415 +0,0 @@ -package org.haxe.lime; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileDescriptor; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.lang.System; -import java.security.MessageDigest; -// import java.util.Hashtable; -import java.util.HashMap; -import java.util.Map; -import java.util.zip.CRC32; - -import android.content.Context; -import android.util.Log; -import android.media.AudioManager; -import android.media.MediaPlayer; -import android.media.SoundPool; -import android.net.Uri; - -class ManagedMediaPlayer -{ - public MediaPlayer mp; - public float leftVol; - public float rightVol; - public boolean isComplete = true; - public int loopsLeft = 0; - public boolean wasPlaying = false; - - public ManagedMediaPlayer(MediaPlayer mp, float leftVol, float rightVol, int loop) { - this.mp = mp; - setVolume(leftVol, rightVol); - isComplete = false; - final ManagedMediaPlayer mmp = this; - - if (loop < 0) { - mp.setLooping(true); - } else if (loop >= 0) { - this.loopsLeft = loop; - mp.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { - @Override public void onCompletion(MediaPlayer mp) { - if (--mmp.loopsLeft > 0) { - mp.seekTo(0); - mp.start(); - } else { - mmp.setComplete(); - } - } - }); - } - } - - public ManagedMediaPlayer setMediaPlayer(MediaPlayer mp) { - this.mp = mp; - return this; - } - - public void setVolume(float leftVol, float rightVol) { - if (mp != null) - mp.setVolume((float)leftVol, (float)rightVol); - this.leftVol = leftVol; - this.rightVol = rightVol; - } - - public int getDuration() { - if (mp != null) - return mp.getDuration(); - return -1; - } - - public int getCurrentPosition() { - if (mp != null) - return mp.getCurrentPosition(); - return -1; - } - - public boolean isPlaying() { - if (mp != null) - return mp.isPlaying(); - return false; - } - - public void pause() { - if (mp != null) - mp.pause(); - } - - public void start() { - if (mp != null) - mp.start(); - } - - public void stop() { - if (mp != null) - mp.stop(); - release(); - } - - public void setComplete() { - this.isComplete = true; - stop(); - } - - public void release() { - if (mp != null) { - mp.release(); - mp = null; - } - } -} - -public class Sound -{ - private static Context mContext; - private static Sound instance; - - private static ManagedMediaPlayer mediaPlayer; - private static boolean mediaPlayerWasPlaying; - private static String mediaPlayerPath; - private static SoundPool mSoundPool; - // private static int mSoundPoolID = 0; - private static long mTimeStamp = 0; - private static HashMap mSoundId; - private static HashMap mSoundProgress; - private static HashMap mSoundDuration; - - public Sound(Context context) - { - if (instance == null) { - mSoundId = new HashMap(); - mSoundProgress = new HashMap(); - mSoundDuration = new HashMap(); - mTimeStamp = System.currentTimeMillis(); - mSoundPool = new SoundPool(8, AudioManager.STREAM_MUSIC, 0); - } - - instance = this; - mContext = context; - } - - public void doPause() - { - if (mSoundPool != null) { - mSoundPool.autoPause(); - } - - if (mediaPlayer != null) { - mediaPlayerWasPlaying = mediaPlayer.isPlaying (); - mediaPlayer.pause(); - } - } - - public void doResume() - { - mTimeStamp = System.currentTimeMillis(); - mSoundPool.autoResume(); - - if (mediaPlayer != null && mediaPlayerWasPlaying) { - mediaPlayer.start (); - } - } - - /* - * Sound effects using SoundPool - * - * This allows for low latency and CPU load but sounds must be 100kB or smaller - */ - - public static int getSoundHandle(String inFilename) - { - int id = GameActivity.getResourceID(inFilename); - Log.v("Sound","Get sound handle ------" + inFilename + " = " + id); - - int index; - - if (id > 0) { - index = mSoundPool.load(mContext, id, 1); - Log.v("Sound", "Loaded index: " + index); - } else { - Log.v("Sound", "Resource not found: " + (-id)); - index = mSoundPool.load(inFilename, 1); - Log.v("Sound", "Loaded index from path: " + index); - } - - int duration = getDuration(inFilename); - mSoundDuration.put(index, (long)duration); - - return index; - } - - public static String getSoundPathByByteArray(byte[] data) throws java.lang.Exception - { - // HACK! It seems that the API doesn't allow to use non file streams. At least with MediaPlayer/SoundPool. - // The alternative is to use an AudioTrack, but the data should be decoded by hand and not sure if android - // provides an API for decoding this kind of stuff. - // So the partial solution at this point is to create a temporary file that will be loaded. - - MessageDigest messageDigest = MessageDigest.getInstance("md5"); - messageDigest.update(data); - String md5 = new java.math.BigInteger(1, messageDigest.digest()).toString(16); - File file = new File(mContext.getCacheDir() + "/" + md5 + ".wav"); - - //File file = File.createTempFile("temp", ".sound", mContext.getFilesDir()); - if (!file.exists()) { - Log.v("Sound", "Created temp sound file :" + file.getAbsolutePath()); - java.io.FileOutputStream fileOutputStream = new java.io.FileOutputStream(file); - fileOutputStream.write(data); - fileOutputStream.flush(); - fileOutputStream.close(); - } else { - Log.v("Sound", "Opened temp sound file :" + file.getAbsolutePath()); - } - - return file.getAbsolutePath(); - } - - public static int playSound(int inResourceID, double inVolLeft, double inVolRight, int inLoop) - { - Log.v("Sound", "PlaySound -----" + inResourceID); - - inLoop--; - if (inLoop < 0) { - inLoop = 0; - } - - if (mSoundId.get(inResourceID) != null) { - //Log.v("VIEW", "Found existing sound " + inResourceID + ", stopping and removing it from progress and id check"); - int a = mSoundId.get(inResourceID); - mSoundPool.stop(a); - mSoundProgress.remove(a); - mSoundId.remove(inResourceID); - } - - int streamId = mSoundPool.play(inResourceID, (float)inVolLeft, (float)inVolRight, 1, inLoop, 1.0f); - mSoundId.put(inResourceID, streamId); - mSoundProgress.put(streamId, (long)0); - return streamId; - } - - static public void stopSound(int inStreamID) - { - if (mSoundPool != null) { - mSoundPool.stop(inStreamID); - mSoundProgress.remove(inStreamID); - mSoundId.values().remove(inStreamID); - } - } - - static public void checkSoundCompletion() - { - long delta = (System.currentTimeMillis() - mTimeStamp); - for (Map.Entry entry : mSoundProgress.entrySet()) { - long val = entry.getValue(); - entry.setValue(val + (long)delta); - } - - mTimeStamp = System.currentTimeMillis(); - } - - static public boolean getSoundComplete(int inSoundID, int inStreamID, int inLoop) { - if (!mSoundProgress.containsKey(inStreamID) || !mSoundDuration.containsKey(inSoundID)) { - return true; - } - - return mSoundProgress.get(inStreamID) >= (mSoundDuration.get(inSoundID) * inLoop); - } - - static public int getSoundPosition(int inSoundID, int inStreamID, int inLoop) { - if (!mSoundProgress.containsKey(inStreamID) || !mSoundDuration.containsKey(inSoundID)) { - return 0; - } - - long progress = mSoundProgress.get(inStreamID); - long total = mSoundDuration.get(inSoundID); - return (int)(progress > (total * inLoop) ? total : (progress % total)); - } - - /* - * Music using MediaPlayer - * - * This allows for larger audio files but consumes more CPU than SoundPool - */ - - private static int getMusicHandle(String inPath) - { - int id = GameActivity.getResourceID(inPath); - Log.v("Sound","Get music handle ------" + inPath + " = " + id); - return id; - } - - private static MediaPlayer createMediaPlayer(String inPath) - { - MediaPlayer mp = null; - int resId = getMusicHandle(inPath); - if (resId < 0) { - if (inPath.charAt(0) == File.separatorChar) { - try { - FileInputStream fis = new FileInputStream(new File(inPath)); - FileDescriptor fd = fis.getFD(); - mp = new MediaPlayer(); - mp.setDataSource(fd); - mp.prepare(); - } catch(FileNotFoundException e) { - System.out.println(e.getMessage()); - return null; - } catch(IOException e) { - System.out.println(e.getMessage()); - return null; - } - } else { - Uri uri = Uri.parse(inPath); - mp = MediaPlayer.create(mContext, uri); - } - } else { - mp = MediaPlayer.create(mContext, resId); - } - - return mp; - } - - public static int playMusic(String inPath, double inVolLeft, double inVolRight, int inLoop, double inStartTime) - { - Log.i("Sound", "playMusic"); - - if (mediaPlayer != null) { - mediaPlayer.stop (); - } - - MediaPlayer mp = createMediaPlayer(inPath); - if (mp == null) { - return -1; - } - - return playMediaPlayer(mp, inPath, inVolLeft, inVolRight, inLoop, inStartTime); - } - - private static int playMediaPlayer(MediaPlayer mp, final String inPath, double inVolLeft, double inVolRight, int inLoop, double inStartTime) - { - mediaPlayer = new ManagedMediaPlayer(mp, (float)inVolLeft, (float)inVolRight, inLoop); - mediaPlayerPath = inPath; - mp.seekTo((int)inStartTime); - mediaPlayer.start(); - - return 0; - } - - public static void stopMusic(String inPath) - { - Log.v("Sound", "stopMusic"); - - if (mediaPlayer != null && inPath.equals(mediaPlayerPath)) { - mediaPlayer.stop (); - } - } - - public static int getDuration(String inPath) - { - int duration = -1; - if (mediaPlayer != null && inPath.equals(mediaPlayerPath)) { - duration = mediaPlayer.getDuration (); - } else { - MediaPlayer mp = createMediaPlayer(inPath); - if (mp != null) { - duration = mp.getDuration(); - mp.release(); - } - } - - return duration; - } - - public static int getPosition(String inPath) - { - if (mediaPlayer != null && inPath.equals(mediaPlayerPath)) { - return mediaPlayer.getCurrentPosition (); - } - return -1; - } - - public static double getLeft(String inPath) - { - if (mediaPlayer != null && inPath.equals(mediaPlayerPath)) { - return mediaPlayer.leftVol; - } - - return 0.5; - } - - public static double getRight(String inPath) - { - if (mediaPlayer != null && inPath.equals(mediaPlayerPath)) { - return mediaPlayer.rightVol; - } - - return 0.5; - } - - public static boolean getComplete(String inPath) - { - if (mediaPlayer != null && inPath.equals(mediaPlayerPath)) { - return mediaPlayer.isComplete; - } - - return true; - } - - public static void setMusicTransform(String inPath, double inVolLeft, double inVolRight) - { - if (mediaPlayer != null && inPath.equals(mediaPlayerPath)) { - mediaPlayer.setVolume((float)inVolLeft, (float)inVolRight); - } - } -} - \ No newline at end of file diff --git a/templates/android/template/src/org/libsdl/app/SDLActivity.java b/templates/android/template/src/org/libsdl/app/SDLActivity.java new file mode 100644 index 000000000..4b6129b4c --- /dev/null +++ b/templates/android/template/src/org/libsdl/app/SDLActivity.java @@ -0,0 +1,1410 @@ +package org.libsdl.app; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.lang.reflect.Method; + +import android.app.*; +import android.content.*; +import android.view.*; +import android.view.inputmethod.BaseInputConnection; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import android.view.inputmethod.InputMethodManager; +import android.widget.AbsoluteLayout; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.os.*; +import android.util.Log; +import android.util.SparseArray; +import android.graphics.*; +import android.graphics.drawable.Drawable; +import android.media.*; +import android.hardware.*; + +import org.haxe.HXCPP; + +/** + SDL Activity +*/ +public class SDLActivity extends Activity { + private static final String TAG = "SDL"; + + // Keep track of the paused state + public static boolean mIsPaused, mIsSurfaceReady, mHasFocus; + public static boolean mExitCalledFromJava; + + // Main components + protected static SDLActivity mSingleton; + protected static SDLSurface mSurface; + protected static View mTextEdit; + protected static ViewGroup mLayout; + protected static SDLJoystickHandler mJoystickHandler; + + // This is what SDL runs in. It invokes SDL_main(), eventually + protected static Thread mSDLThread; + + // Audio + protected static AudioTrack mAudioTrack; + + // Load the .so + static { + + ::foreach ndlls:: + System.loadLibrary ("::name::"); + ::end:: + + } + + /** + * This method is called by SDL using JNI. + * This method is called by SDL before starting the native application thread. + * It can be overridden to provide the arguments after the application name. + * The default implementation returns an empty array. It never returns null. + * @return arguments for the native application. + */ + protected String[] getArguments() { + return new String[0]; + } + + public static void initialize() { + // The static nature of the singleton and Android quirkyness force us to initialize everything here + // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values + mSingleton = null; + mSurface = null; + mTextEdit = null; + mLayout = null; + mJoystickHandler = null; + mSDLThread = null; + mAudioTrack = null; + mExitCalledFromJava = false; + mIsPaused = false; + mIsSurfaceReady = false; + mHasFocus = true; + } + + // Setup + @Override + protected void onCreate(Bundle savedInstanceState) { + Log.v("SDL", "onCreate():" + mSingleton); + super.onCreate(savedInstanceState); + + SDLActivity.initialize(); + // So we can call stuff from static callbacks + mSingleton = this; + + // Set up the surface + mSurface = new SDLSurface(getApplication()); + + if(Build.VERSION.SDK_INT >= 12) { + mJoystickHandler = new SDLJoystickHandler_API12(); + } + else { + mJoystickHandler = new SDLJoystickHandler(); + } + + mLayout = new AbsoluteLayout(this); + mLayout.addView(mSurface); + + setContentView(mLayout); + } + + // Events + @Override + protected void onPause() { + Log.v("SDL", "onPause()"); + super.onPause(); + SDLActivity.handlePause(); + } + + @Override + protected void onResume() { + Log.v("SDL", "onResume()"); + super.onResume(); + SDLActivity.handleResume(); + } + + @Override + public void onLowMemory() { + Log.v("SDL", "onLowMemory()"); + super.onLowMemory(); + SDLActivity.nativeLowMemory(); + } + + @Override + protected void onDestroy() { + Log.v("SDL", "onDestroy()"); + // Send a quit message to the application + SDLActivity.mExitCalledFromJava = true; + SDLActivity.nativeQuit(); + + // Now wait for the SDL thread to quit + if (SDLActivity.mSDLThread != null) { + try { + SDLActivity.mSDLThread.join(); + } catch(Exception e) { + Log.v("SDL", "Problem stopping thread: " + e); + } + SDLActivity.mSDLThread = null; + + //Log.v("SDL", "Finished waiting for SDL thread"); + } + + super.onDestroy(); + // Reset everything in case the user re opens the app + SDLActivity.initialize(); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + int keyCode = event.getKeyCode(); + // Ignore certain special keys so they're handled by Android + if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || + keyCode == KeyEvent.KEYCODE_VOLUME_UP || + keyCode == KeyEvent.KEYCODE_CAMERA || + keyCode == 168 || /* API 11: KeyEvent.KEYCODE_ZOOM_IN */ + keyCode == 169 /* API 11: KeyEvent.KEYCODE_ZOOM_OUT */ + ) { + return false; + } + return super.dispatchKeyEvent(event); + } + + /** Called by onPause or surfaceDestroyed. Even if surfaceDestroyed + * is the first to be called, mIsSurfaceReady should still be set + * to 'true' during the call to onPause (in a usual scenario). + */ + public static void handlePause() { + if (!SDLActivity.mIsPaused && SDLActivity.mIsSurfaceReady) { + SDLActivity.mIsPaused = true; + SDLActivity.nativePause(); + mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, false); + } + } + + /** Called by onResume or surfaceCreated. An actual resume should be done only when the surface is ready. + * Note: Some Android variants may send multiple surfaceChanged events, so we don't need to resume + * every time we get one of those events, only if it comes after surfaceDestroyed + */ + public static void handleResume() { + if (SDLActivity.mIsPaused && SDLActivity.mIsSurfaceReady && SDLActivity.mHasFocus) { + SDLActivity.mIsPaused = false; + SDLActivity.nativeResume(); + mSurface.handleResume(); + } + } + + /* The native thread has finished */ + public static void handleNativeExit() { + SDLActivity.mSDLThread = null; + mSingleton.finish(); + } + + + // Messages from the SDLMain thread + static final int COMMAND_CHANGE_TITLE = 1; + static final int COMMAND_UNUSED = 2; + static final int COMMAND_TEXTEDIT_HIDE = 3; + static final int COMMAND_SET_KEEP_SCREEN_ON = 5; + + protected static final int COMMAND_USER = 0x8000; + + /** + * This method is called by SDL if SDL did not handle a message itself. + * This happens if a received message contains an unsupported command. + * Method can be overwritten to handle Messages in a different class. + * @param command the command of the message. + * @param param the parameter of the message. May be null. + * @return if the message was handled in overridden method. + */ + protected boolean onUnhandledMessage(int command, Object param) { + return false; + } + + /** + * A Handler class for Messages from native SDL applications. + * It uses current Activities as target (e.g. for the title). + * static to prevent implicit references to enclosing object. + */ + protected static class SDLCommandHandler extends Handler { + @Override + public void handleMessage(Message msg) { + Context context = getContext(); + if (context == null) { + Log.e(TAG, "error handling message, getContext() returned null"); + return; + } + switch (msg.arg1) { + case COMMAND_CHANGE_TITLE: + if (context instanceof Activity) { + ((Activity) context).setTitle((String)msg.obj); + } else { + Log.e(TAG, "error handling message, getContext() returned no Activity"); + } + break; + case COMMAND_TEXTEDIT_HIDE: + if (mTextEdit != null) { + mTextEdit.setVisibility(View.GONE); + + InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0); + } + break; + case COMMAND_SET_KEEP_SCREEN_ON: + { + Window window = ((Activity) context).getWindow(); + if (window != null) { + if ((msg.obj instanceof Integer) && (((Integer) msg.obj).intValue() != 0)) { + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } else { + window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + } + break; + } + default: + if ((context instanceof SDLActivity) && !((SDLActivity) context).onUnhandledMessage(msg.arg1, msg.obj)) { + Log.e(TAG, "error handling message, command is " + msg.arg1); + } + } + } + } + + // Handler for the messages + Handler commandHandler = new SDLCommandHandler(); + + // Send a message from the SDLMain thread + boolean sendCommand(int command, Object data) { + Message msg = commandHandler.obtainMessage(); + msg.arg1 = command; + msg.obj = data; + return commandHandler.sendMessage(msg); + } + + // C functions we call + public static native int nativeInit(Object arguments); + public static native void nativeLowMemory(); + public static native void nativeQuit(); + public static native void nativePause(); + public static native void nativeResume(); + public static native void onNativeResize(int x, int y, int format); + public static native int onNativePadDown(int device_id, int keycode); + public static native int onNativePadUp(int device_id, int keycode); + public static native void onNativeJoy(int device_id, int axis, + float value); + public static native void onNativeHat(int device_id, int hat_id, + int x, int y); + public static native void onNativeKeyDown(int keycode); + public static native void onNativeKeyUp(int keycode); + public static native void onNativeKeyboardFocusLost(); + public static native void onNativeTouch(int touchDevId, int pointerFingerId, + int action, float x, + float y, float p); + public static native void onNativeAccel(float x, float y, float z); + public static native void onNativeSurfaceChanged(); + public static native void onNativeSurfaceDestroyed(); + public static native void nativeFlipBuffers(); + public static native int nativeAddJoystick(int device_id, String name, + int is_accelerometer, int nbuttons, + int naxes, int nhats, int nballs); + public static native int nativeRemoveJoystick(int device_id); + public static native String nativeGetHint(String name); + + /** + * This method is called by SDL using JNI. + */ + public static void flipBuffers() { + SDLActivity.nativeFlipBuffers(); + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean setActivityTitle(String title) { + // Called from SDLMain() thread and can't directly affect the view + return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title); + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean sendMessage(int command, int param) { + return mSingleton.sendCommand(command, Integer.valueOf(param)); + } + + /** + * This method is called by SDL using JNI. + */ + public static Context getContext() { + return mSingleton; + } + + /** + * This method is called by SDL using JNI. + * @return result of getSystemService(name) but executed on UI thread. + */ + public Object getSystemServiceFromUiThread(final String name) { + final Object lock = new Object(); + final Object[] results = new Object[2]; // array for writable variables + synchronized (lock) { + runOnUiThread(new Runnable() { + @Override + public void run() { + synchronized (lock) { + results[0] = getSystemService(name); + results[1] = Boolean.TRUE; + lock.notify(); + } + } + }); + if (results[1] == null) { + try { + lock.wait(); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + } + } + return results[0]; + } + + static class ShowTextInputTask implements Runnable { + /* + * This is used to regulate the pan&scan method to have some offset from + * the bottom edge of the input region and the top edge of an input + * method (soft keyboard) + */ + static final int HEIGHT_PADDING = 15; + + public int x, y, w, h; + + public ShowTextInputTask(int x, int y, int w, int h) { + this.x = x; + this.y = y; + this.w = w; + this.h = h; + } + + @Override + public void run() { + AbsoluteLayout.LayoutParams params = new AbsoluteLayout.LayoutParams( + w, h + HEIGHT_PADDING, x, y); + + if (mTextEdit == null) { + mTextEdit = new DummyEdit(getContext()); + + mLayout.addView(mTextEdit, params); + } else { + mTextEdit.setLayoutParams(params); + } + + mTextEdit.setVisibility(View.VISIBLE); + mTextEdit.requestFocus(); + + InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(mTextEdit, 0); + } + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean showTextInput(int x, int y, int w, int h) { + // Transfer the task to the main thread as a Runnable + return mSingleton.commandHandler.post(new ShowTextInputTask(x, y, w, h)); + } + + /** + * This method is called by SDL using JNI. + */ + public static Surface getNativeSurface() { + return SDLActivity.mSurface.getNativeSurface(); + } + + // Audio + + /** + * This method is called by SDL using JNI. + */ + public static int audioInit(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) { + int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO; + int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT; + int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1); + + Log.v("SDL", "SDL audio: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + (sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer"); + + // Let the user pick a larger buffer if they really want -- but ye + // gods they probably shouldn't, the minimums are horrifyingly high + // latency already + desiredFrames = Math.max(desiredFrames, (AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize); + + if (mAudioTrack == null) { + mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, + channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM); + + // Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid + // Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java + // Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState() + + if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) { + Log.e("SDL", "Failed during initialization of Audio Track"); + mAudioTrack = null; + return -1; + } + + mAudioTrack.play(); + } + + Log.v("SDL", "SDL audio: got " + ((mAudioTrack.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioTrack.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + (mAudioTrack.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer"); + + return 0; + } + + /** + * This method is called by SDL using JNI. + */ + public static void audioWriteShortBuffer(short[] buffer) { + for (int i = 0; i < buffer.length; ) { + int result = mAudioTrack.write(buffer, i, buffer.length - i); + if (result > 0) { + i += result; + } else if (result == 0) { + try { + Thread.sleep(1); + } catch(InterruptedException e) { + // Nom nom + } + } else { + Log.w("SDL", "SDL audio: error return from write(short)"); + return; + } + } + } + + /** + * This method is called by SDL using JNI. + */ + public static void audioWriteByteBuffer(byte[] buffer) { + for (int i = 0; i < buffer.length; ) { + int result = mAudioTrack.write(buffer, i, buffer.length - i); + if (result > 0) { + i += result; + } else if (result == 0) { + try { + Thread.sleep(1); + } catch(InterruptedException e) { + // Nom nom + } + } else { + Log.w("SDL", "SDL audio: error return from write(byte)"); + return; + } + } + } + + /** + * This method is called by SDL using JNI. + */ + public static void audioQuit() { + if (mAudioTrack != null) { + mAudioTrack.stop(); + mAudioTrack = null; + } + } + + // Input + + /** + * This method is called by SDL using JNI. + * @return an array which may be empty but is never null. + */ + public static int[] inputGetInputDeviceIds(int sources) { + int[] ids = InputDevice.getDeviceIds(); + int[] filtered = new int[ids.length]; + int used = 0; + for (int i = 0; i < ids.length; ++i) { + InputDevice device = InputDevice.getDevice(ids[i]); + if ((device != null) && ((device.getSources() & sources) != 0)) { + filtered[used++] = device.getId(); + } + } + return Arrays.copyOf(filtered, used); + } + + // Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance + public static boolean handleJoystickMotionEvent(MotionEvent event) { + return mJoystickHandler.handleMotionEvent(event); + } + + /** + * This method is called by SDL using JNI. + */ + public static void pollInputDevices() { + if (SDLActivity.mSDLThread != null) { + mJoystickHandler.pollInputDevices(); + } + } + + // APK extension files support + + /** com.android.vending.expansion.zipfile.ZipResourceFile object or null. */ + private Object expansionFile; + + /** com.android.vending.expansion.zipfile.ZipResourceFile's getInputStream() or null. */ + private Method expansionFileMethod; + + /** + * This method is called by SDL using JNI. + */ + public InputStream openAPKExtensionInputStream(String fileName) throws IOException { + // Get a ZipResourceFile representing a merger of both the main and patch files + if (expansionFile == null) { + Integer mainVersion = Integer.valueOf(nativeGetHint("SDL_ANDROID_APK_EXPANSION_MAIN_FILE_VERSION")); + Integer patchVersion = Integer.valueOf(nativeGetHint("SDL_ANDROID_APK_EXPANSION_PATCH_FILE_VERSION")); + + try { + // To avoid direct dependency on Google APK extension library that is + // not a part of Android SDK we access it using reflection + expansionFile = Class.forName("com.android.vending.expansion.zipfile.APKExpansionSupport") + .getMethod("getAPKExpansionZipFile", Context.class, int.class, int.class) + .invoke(null, this, mainVersion, patchVersion); + + expansionFileMethod = expansionFile.getClass() + .getMethod("getInputStream", String.class); + } catch (Exception ex) { + ex.printStackTrace(); + expansionFile = null; + expansionFileMethod = null; + } + } + + // Get an input stream for a known file inside the expansion file ZIPs + InputStream fileStream; + try { + fileStream = (InputStream)expansionFileMethod.invoke(expansionFile, fileName); + } catch (Exception ex) { + ex.printStackTrace(); + fileStream = null; + } + + if (fileStream == null) { + throw new IOException(); + } + + return fileStream; + } + + // Messagebox + + /** Result of current messagebox. Also used for blocking the calling thread. */ + protected final int[] messageboxSelection = new int[1]; + + /** Id of current dialog. */ + protected int dialogs = 0; + + /** + * This method is called by SDL using JNI. + * Shows the messagebox from UI thread and block calling thread. + * buttonFlags, buttonIds and buttonTexts must have same length. + * @param buttonFlags array containing flags for every button. + * @param buttonIds array containing id for every button. + * @param buttonTexts array containing text for every button. + * @param colors null for default or array of length 5 containing colors. + * @return button id or -1. + */ + public int messageboxShowMessageBox( + final int flags, + final String title, + final String message, + final int[] buttonFlags, + final int[] buttonIds, + final String[] buttonTexts, + final int[] colors) { + + messageboxSelection[0] = -1; + + // sanity checks + + if ((buttonFlags.length != buttonIds.length) && (buttonIds.length != buttonTexts.length)) { + return -1; // implementation broken + } + + // collect arguments for Dialog + + final Bundle args = new Bundle(); + args.putInt("flags", flags); + args.putString("title", title); + args.putString("message", message); + args.putIntArray("buttonFlags", buttonFlags); + args.putIntArray("buttonIds", buttonIds); + args.putStringArray("buttonTexts", buttonTexts); + args.putIntArray("colors", colors); + + // trigger Dialog creation on UI thread + + runOnUiThread(new Runnable() { + @Override + public void run() { + showDialog(dialogs++, args); + } + }); + + // block the calling thread + + synchronized (messageboxSelection) { + try { + messageboxSelection.wait(); + } catch (InterruptedException ex) { + ex.printStackTrace(); + return -1; + } + } + + // return selected value + + return messageboxSelection[0]; + } + + @Override + protected Dialog onCreateDialog(int ignore, Bundle args) { + + // TODO set values from "flags" to messagebox dialog + + // get colors + + int[] colors = args.getIntArray("colors"); + int backgroundColor; + int textColor; + int buttonBorderColor; + int buttonBackgroundColor; + int buttonSelectedColor; + if (colors != null) { + int i = -1; + backgroundColor = colors[++i]; + textColor = colors[++i]; + buttonBorderColor = colors[++i]; + buttonBackgroundColor = colors[++i]; + buttonSelectedColor = colors[++i]; + } else { + backgroundColor = Color.TRANSPARENT; + textColor = Color.TRANSPARENT; + buttonBorderColor = Color.TRANSPARENT; + buttonBackgroundColor = Color.TRANSPARENT; + buttonSelectedColor = Color.TRANSPARENT; + } + + // create dialog with title and a listener to wake up calling thread + + final Dialog dialog = new Dialog(this); + dialog.setTitle(args.getString("title")); + dialog.setCancelable(false); + dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface unused) { + synchronized (messageboxSelection) { + messageboxSelection.notify(); + } + } + }); + + // create text + + TextView message = new TextView(this); + message.setGravity(Gravity.CENTER); + message.setText(args.getString("message")); + if (textColor != Color.TRANSPARENT) { + message.setTextColor(textColor); +} + + // create buttons + + int[] buttonFlags = args.getIntArray("buttonFlags"); + int[] buttonIds = args.getIntArray("buttonIds"); + String[] buttonTexts = args.getStringArray("buttonTexts"); + + final SparseArray