From: Rudolf Polzer Date: Wed, 20 Jul 2016 13:28:47 +0000 (-0400) Subject: Android project files (incomplete). X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=a00e32dfd4e731e68477ccaf447a0de25e0dd76b;p=xonotic%2Fdarkplaces.git Android project files (incomplete). --- diff --git a/Android/.gitignore b/Android/.gitignore new file mode 100644 index 00000000..6e9d8b97 --- /dev/null +++ b/Android/.gitignore @@ -0,0 +1,4 @@ +local.properties +jni/SDL +jni/ogg/libogg-* +jni/vorbis/libvorbis-* diff --git a/Android/AndroidManifest.xml b/Android/AndroidManifest.xml new file mode 100644 index 00000000..43c91f99 --- /dev/null +++ b/Android/AndroidManifest.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Android/ant.properties b/Android/ant.properties new file mode 100644 index 00000000..b0971e89 --- /dev/null +++ b/Android/ant.properties @@ -0,0 +1,17 @@ +# This file is used to override default values used by the Ant build system. +# +# This file must be checked into Version Control Systems, as it is +# integral to the build system of your project. + +# This file is only used by the Ant script. + +# You can use this to override default values such as +# 'source.dir' for the location of your java source folder and +# 'out.dir' for the location of your output folder. + +# You can also use it define how the release builds are signed by declaring +# the following properties: +# 'key.store' for the location of your keystore and +# 'key.alias' for the name of the key to use. +# The password will be asked during the build when you use the 'release' target. + diff --git a/Android/build.properties b/Android/build.properties new file mode 100644 index 00000000..edc7f230 --- /dev/null +++ b/Android/build.properties @@ -0,0 +1,17 @@ +# This file is used to override default values used by the Ant build system. +# +# This file must be checked in Version Control Systems, as it is +# integral to the build system of your project. + +# This file is only used by the Ant script. + +# You can use this to override default values such as +# 'source.dir' for the location of your java source folder and +# 'out.dir' for the location of your output folder. + +# You can also use it define how the release builds are signed by declaring +# the following properties: +# 'key.store' for the location of your keystore and +# 'key.alias' for the name of the key to use. +# The password will be asked during the build when you use the 'release' target. + diff --git a/Android/build.xml b/Android/build.xml new file mode 100644 index 00000000..9f19a077 --- /dev/null +++ b/Android/build.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Android/default.properties b/Android/default.properties new file mode 100644 index 00000000..0cdab956 --- /dev/null +++ b/Android/default.properties @@ -0,0 +1,11 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system use, +# "build.properties", and override values to adapt the script to your +# project structure. + +# Project target. +target=android-12 diff --git a/Android/jni/Android.mk b/Android/jni/Android.mk new file mode 100644 index 00000000..5053e7d6 --- /dev/null +++ b/Android/jni/Android.mk @@ -0,0 +1 @@ +include $(call all-subdir-makefiles) diff --git a/Android/jni/Application.mk b/Android/jni/Application.mk new file mode 100644 index 00000000..961e19a5 --- /dev/null +++ b/Android/jni/Application.mk @@ -0,0 +1,6 @@ + +# Uncomment this if you're using STL in your project +# See CPLUSPLUS-SUPPORT.html in the NDK documentation for more information +# APP_STL := stlport_static + +APP_ABI := all diff --git a/Android/jni/ogg/Android.mk b/Android/jni/ogg/Android.mk new file mode 100755 index 00000000..8c5778a3 --- /dev/null +++ b/Android/jni/ogg/Android.mk @@ -0,0 +1,21 @@ +LOCAL_PATH := $(call my-dir) + +########################### +# +# libogg shared library +# +########################### + +include $(CLEAR_VARS) + +LOCAL_MODULE := ogg + +LOCAL_C_INCLUDES := $(LOCAL_PATH)/libogg-1.3.2/include + +LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES) + +LOCAL_SRC_FILES := \ + $(subst $(LOCAL_PATH)/,, \ + $(wildcard $(LOCAL_PATH)/libogg-1.3.2/src/*.c)) + +include $(BUILD_SHARED_LIBRARY) diff --git a/Android/jni/src/Android.mk b/Android/jni/src/Android.mk new file mode 100644 index 00000000..45f86285 --- /dev/null +++ b/Android/jni/src/Android.mk @@ -0,0 +1,137 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := main + +SDL_PATH := ../SDL +LIBOGG_PATH := ../ogg/libogg-1.3.2 +LIBVORBIS_PATH := ../vorbis/libvorbis-1.3.5 +DARKPLACES_PATH := ../../.. + +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/$(SDL_PATH)/include \ + $(LOCAL_PATH)/$(LIBOGG_PATH)/include \ + $(LOCAL_PATH)/$(LIBVORBIS_PATH)/include + +LOCAL_CFLAGS := \ + -D_FILE_OFFSET_BITS=64 \ + -D__KERNEL_STRICT_NAMES \ + -DCONFIG_MENU \ + -DCONFIG_VIDEO_CAPTURE + +# Add your application source files here... +# Note: this is the expansion of $(OBJ_CD) in Darkplaces's own Makefile. +LOCAL_SRC_FILES := $(SDL_PATH)/src/main/android/SDL_android_main.c \ + \ + $(DARKPLACES_PATH)/builddate.c \ + $(DARKPLACES_PATH)/sys_sdl.c \ + $(DARKPLACES_PATH)/vid_sdl.c \ + $(DARKPLACES_PATH)/thread_sdl.c \ + \ + $(DARKPLACES_PATH)/menu.c \ + $(DARKPLACES_PATH)/mvm_cmds.c \ + \ + $(DARKPLACES_PATH)/snd_main.c \ + $(DARKPLACES_PATH)/snd_mem.c \ + $(DARKPLACES_PATH)/snd_mix.c \ + $(DARKPLACES_PATH)/snd_ogg.c \ + $(DARKPLACES_PATH)/snd_wav.c \ + \ + $(DARKPLACES_PATH)/snd_sdl.c \ + \ + $(DARKPLACES_PATH)/cd_shared.c \ + $(DARKPLACES_PATH)/cd_null.c \ + \ + $(DARKPLACES_PATH)/cap_avi.c \ + $(DARKPLACES_PATH)/cap_ogg.c \ + \ + $(DARKPLACES_PATH)/bih.c \ + $(DARKPLACES_PATH)/crypto.c \ + $(DARKPLACES_PATH)/cl_collision.c \ + $(DARKPLACES_PATH)/cl_demo.c \ + $(DARKPLACES_PATH)/cl_dyntexture.c \ + $(DARKPLACES_PATH)/cl_input.c \ + $(DARKPLACES_PATH)/cl_main.c \ + $(DARKPLACES_PATH)/cl_parse.c \ + $(DARKPLACES_PATH)/cl_particles.c \ + $(DARKPLACES_PATH)/cl_screen.c \ + $(DARKPLACES_PATH)/cl_video.c \ + $(DARKPLACES_PATH)/clvm_cmds.c \ + $(DARKPLACES_PATH)/cmd.c \ + $(DARKPLACES_PATH)/collision.c \ + $(DARKPLACES_PATH)/common.c \ + $(DARKPLACES_PATH)/console.c \ + $(DARKPLACES_PATH)/csprogs.c \ + $(DARKPLACES_PATH)/curves.c \ + $(DARKPLACES_PATH)/cvar.c \ + $(DARKPLACES_PATH)/dpsoftrast.c \ + $(DARKPLACES_PATH)/dpvsimpledecode.c \ + $(DARKPLACES_PATH)/filematch.c \ + $(DARKPLACES_PATH)/fractalnoise.c \ + $(DARKPLACES_PATH)/fs.c \ + $(DARKPLACES_PATH)/ft2.c \ + $(DARKPLACES_PATH)/utf8lib.c \ + $(DARKPLACES_PATH)/gl_backend.c \ + $(DARKPLACES_PATH)/gl_draw.c \ + $(DARKPLACES_PATH)/gl_rmain.c \ + $(DARKPLACES_PATH)/gl_rsurf.c \ + $(DARKPLACES_PATH)/gl_textures.c \ + $(DARKPLACES_PATH)/hmac.c \ + $(DARKPLACES_PATH)/host.c \ + $(DARKPLACES_PATH)/host_cmd.c \ + $(DARKPLACES_PATH)/image.c \ + $(DARKPLACES_PATH)/image_png.c \ + $(DARKPLACES_PATH)/jpeg.c \ + $(DARKPLACES_PATH)/keys.c \ + $(DARKPLACES_PATH)/lhnet.c \ + $(DARKPLACES_PATH)/libcurl.c \ + $(DARKPLACES_PATH)/mathlib.c \ + $(DARKPLACES_PATH)/matrixlib.c \ + $(DARKPLACES_PATH)/mdfour.c \ + $(DARKPLACES_PATH)/meshqueue.c \ + $(DARKPLACES_PATH)/mod_skeletal_animatevertices_sse.c \ + $(DARKPLACES_PATH)/mod_skeletal_animatevertices_generic.c \ + $(DARKPLACES_PATH)/model_alias.c \ + $(DARKPLACES_PATH)/model_brush.c \ + $(DARKPLACES_PATH)/model_shared.c \ + $(DARKPLACES_PATH)/model_sprite.c \ + $(DARKPLACES_PATH)/netconn.c \ + $(DARKPLACES_PATH)/palette.c \ + $(DARKPLACES_PATH)/polygon.c \ + $(DARKPLACES_PATH)/portals.c \ + $(DARKPLACES_PATH)/protocol.c \ + $(DARKPLACES_PATH)/prvm_cmds.c \ + $(DARKPLACES_PATH)/prvm_edict.c \ + $(DARKPLACES_PATH)/prvm_exec.c \ + $(DARKPLACES_PATH)/r_explosion.c \ + $(DARKPLACES_PATH)/r_lerpanim.c \ + $(DARKPLACES_PATH)/r_lightning.c \ + $(DARKPLACES_PATH)/r_modules.c \ + $(DARKPLACES_PATH)/r_shadow.c \ + $(DARKPLACES_PATH)/r_sky.c \ + $(DARKPLACES_PATH)/r_sprites.c \ + $(DARKPLACES_PATH)/sbar.c \ + $(DARKPLACES_PATH)/sv_demo.c \ + $(DARKPLACES_PATH)/sv_main.c \ + $(DARKPLACES_PATH)/sv_move.c \ + $(DARKPLACES_PATH)/sv_phys.c \ + $(DARKPLACES_PATH)/sv_user.c \ + $(DARKPLACES_PATH)/svbsp.c \ + $(DARKPLACES_PATH)/svvm_cmds.c \ + $(DARKPLACES_PATH)/sys_shared.c \ + $(DARKPLACES_PATH)/vid_shared.c \ + $(DARKPLACES_PATH)/view.c \ + $(DARKPLACES_PATH)/wad.c \ + $(DARKPLACES_PATH)/world.c \ + $(DARKPLACES_PATH)/zone.c + +LOCAL_SHARED_LIBRARIES := \ + SDL2 \ + ogg \ + vorbis + +LOCAL_LDLIBS := \ + -lGLESv1_CM -lGLESv2 -llog -lz + +include $(BUILD_SHARED_LIBRARY) diff --git a/Android/jni/vorbis/Android.mk b/Android/jni/vorbis/Android.mk new file mode 100755 index 00000000..677dadc2 --- /dev/null +++ b/Android/jni/vorbis/Android.mk @@ -0,0 +1,49 @@ +LOCAL_PATH := $(call my-dir) + +########################### +# +# libvorbis shared library +# +########################### + +include $(CLEAR_VARS) + +LOCAL_MODULE := vorbis + +LIBOGG_PATH := ../ogg/libogg-1.3.2 + +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/libvorbis-1.3.5/include \ + $(LOCAL_PATH)/libvorbis-1.3.5/libs \ + $(LOCAL_PATH)/$(LIBOGG_PATH)/include + +LOCAL_EXPORT_C_INCLUDES := \ + $(LOCAL_PATH)/include \ + +LOCAL_SRC_FILES := \ + libvorbis-1.3.5/lib/mdct.c \ + libvorbis-1.3.5/lib/smallft.c \ + libvorbis-1.3.5/lib/block.c \ + libvorbis-1.3.5/lib/envelope.c \ + libvorbis-1.3.5/lib/window.c \ + libvorbis-1.3.5/lib/lsp.c \ + libvorbis-1.3.5/lib/lpc.c \ + libvorbis-1.3.5/lib/analysis.c \ + libvorbis-1.3.5/lib/synthesis.c \ + libvorbis-1.3.5/lib/psy.c \ + libvorbis-1.3.5/lib/info.c \ + libvorbis-1.3.5/lib/floor1.c \ + libvorbis-1.3.5/lib/floor0.c \ + libvorbis-1.3.5/lib/res0.c \ + libvorbis-1.3.5/lib/mapping0.c \ + libvorbis-1.3.5/lib/registry.c \ + libvorbis-1.3.5/lib/codebook.c \ + libvorbis-1.3.5/lib/sharedbook.c \ + libvorbis-1.3.5/lib/lookup.c \ + libvorbis-1.3.5/lib/bitrate.c \ + libvorbis-1.3.5/lib/vorbisfile.c + +LOCAL_SHARED_LIBRARIES := \ + ogg + +include $(BUILD_SHARED_LIBRARY) diff --git a/Android/proguard-project.txt b/Android/proguard-project.txt new file mode 100644 index 00000000..f2fe1559 --- /dev/null +++ b/Android/proguard-project.txt @@ -0,0 +1,20 @@ +# To enable ProGuard in your project, edit project.properties +# to define the proguard.config property as described in that file. +# +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in ${sdk.dir}/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the ProGuard +# include property in project.properties. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/Android/project.properties b/Android/project.properties new file mode 100644 index 00000000..0f507e53 --- /dev/null +++ b/Android/project.properties @@ -0,0 +1,14 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-12 diff --git a/Android/res/drawable-hdpi/ic_launcher.png b/Android/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 00000000..d50bdaae Binary files /dev/null and b/Android/res/drawable-hdpi/ic_launcher.png differ diff --git a/Android/res/drawable-mdpi/ic_launcher.png b/Android/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 00000000..0a299eb3 Binary files /dev/null and b/Android/res/drawable-mdpi/ic_launcher.png differ diff --git a/Android/res/drawable-xhdpi/ic_launcher.png b/Android/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 00000000..a336ad5c Binary files /dev/null and b/Android/res/drawable-xhdpi/ic_launcher.png differ diff --git a/Android/res/drawable-xxhdpi/ic_launcher.png b/Android/res/drawable-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..d423dac2 Binary files /dev/null and b/Android/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/Android/res/layout/main.xml b/Android/res/layout/main.xml new file mode 100644 index 00000000..123c4b6e --- /dev/null +++ b/Android/res/layout/main.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/Android/res/values/strings.xml b/Android/res/values/strings.xml new file mode 100644 index 00000000..9bce51cb --- /dev/null +++ b/Android/res/values/strings.xml @@ -0,0 +1,4 @@ + + + SDL App + diff --git a/Android/src/org/libsdl/app/SDLActivity.java b/Android/src/org/libsdl/app/SDLActivity.java new file mode 100644 index 00000000..bc81b03e --- /dev/null +++ b/Android/src/org/libsdl/app/SDLActivity.java @@ -0,0 +1,1674 @@ +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.text.InputType; +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 android.content.pm.ActivityInfo; + +/** + 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; + + /** If shared libraries (e.g. SDL or the native application) could not be loaded. */ + public static boolean mBrokenLibraries; + + // If we want to separate mouse and touch events. + // This is only toggled in native code when a hint is set! + public static boolean mSeparateMouseAndTouch; + + // 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; + + /** + * This method is called by SDL before loading the native shared libraries. + * It can be overridden to provide names of shared libraries to be loaded. + * The default implementation returns the defaults. It never returns null. + * An array returned by a new implementation must at least contain "SDL2". + * Also keep in mind that the order the libraries are loaded may matter. + * @return names of shared libraries to be loaded (e.g. "SDL2", "main"). + */ + protected String[] getLibraries() { + return new String[] { + "SDL2", + // "SDL2_image", + // "SDL2_mixer", + // "SDL2_net", + // "SDL2_ttf", + "main" + }; + } + + // Load the .so + public void loadLibraries() { + for (String lib : getLibraries()) { + System.loadLibrary(lib); + } + } + + /** + * 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; + mBrokenLibraries = false; + mIsPaused = false; + mIsSurfaceReady = false; + mHasFocus = true; + } + + // Setup + @Override + protected void onCreate(Bundle savedInstanceState) { + Log.v(TAG, "Device: " + android.os.Build.DEVICE); + Log.v(TAG, "Model: " + android.os.Build.MODEL); + Log.v(TAG, "onCreate(): " + mSingleton); + super.onCreate(savedInstanceState); + + SDLActivity.initialize(); + // So we can call stuff from static callbacks + mSingleton = this; + + // Load shared libraries + String errorMsgBrokenLib = ""; + try { + loadLibraries(); + } catch(UnsatisfiedLinkError e) { + System.err.println(e.getMessage()); + mBrokenLibraries = true; + errorMsgBrokenLib = e.getMessage(); + } catch(Exception e) { + System.err.println(e.getMessage()); + mBrokenLibraries = true; + errorMsgBrokenLib = e.getMessage(); + } + + if (mBrokenLibraries) + { + AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this); + dlgAlert.setMessage("An error occurred while trying to start the application. Please try again and/or reinstall." + + System.getProperty("line.separator") + + System.getProperty("line.separator") + + "Error: " + errorMsgBrokenLib); + dlgAlert.setTitle("SDL Error"); + dlgAlert.setPositiveButton("Exit", + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog,int id) { + // if this button is clicked, close current activity + SDLActivity.mSingleton.finish(); + } + }); + dlgAlert.setCancelable(false); + dlgAlert.create().show(); + + return; + } + + // 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); + + // Get filename from "Open with" of another application + Intent intent = getIntent(); + + if (intent != null && intent.getData() != null) { + String filename = intent.getData().getPath(); + if (filename != null) { + Log.v(TAG, "Got filename: " + filename); + SDLActivity.onNativeDropFile(filename); + } + } + } + + // Events + @Override + protected void onPause() { + Log.v(TAG, "onPause()"); + super.onPause(); + + if (SDLActivity.mBrokenLibraries) { + return; + } + + SDLActivity.handlePause(); + } + + @Override + protected void onResume() { + Log.v(TAG, "onResume()"); + super.onResume(); + + if (SDLActivity.mBrokenLibraries) { + return; + } + + SDLActivity.handleResume(); + } + + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + Log.v(TAG, "onWindowFocusChanged(): " + hasFocus); + + if (SDLActivity.mBrokenLibraries) { + return; + } + + SDLActivity.mHasFocus = hasFocus; + if (hasFocus) { + SDLActivity.handleResume(); + } + } + + @Override + public void onLowMemory() { + Log.v(TAG, "onLowMemory()"); + super.onLowMemory(); + + if (SDLActivity.mBrokenLibraries) { + return; + } + + SDLActivity.nativeLowMemory(); + } + + @Override + protected void onDestroy() { + Log.v(TAG, "onDestroy()"); + + if (SDLActivity.mBrokenLibraries) { + super.onDestroy(); + // Reset everything in case the user re opens the app + SDLActivity.initialize(); + return; + } + + // 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(TAG, "Problem stopping thread: " + e); + } + SDLActivity.mSDLThread = null; + + //Log.v(TAG, "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) { + + if (SDLActivity.mBrokenLibraries) { + return false; + } + + 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.handlePause(); + } + } + + /** 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 onNativeDropFile(String filename); + public static native void onNativeResize(int x, int y, int format, float rate); + 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 onNativeMouse(int button, int action, float x, float y); + 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 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 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(TAG, "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(TAG, "Failed during initialization of Audio Track"); + mAudioTrack = null; + return -1; + } + + mAudioTrack.play(); + } + + Log.v(TAG, "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(TAG, "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(TAG, "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 expansion 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 was called by SDL using JNI. + * @deprecated because of an incorrect name + */ + @Deprecated + public InputStream openAPKExtensionInputStream(String fileName) throws IOException { + return openAPKExpansionInputStream(fileName); + } + + /** + * This method is called by SDL using JNI. + * @return an InputStream on success or null if no expansion file was used. + * @throws IOException on errors. Message is set for the SDL error message. + */ + public InputStream openAPKExpansionInputStream(String fileName) throws IOException { + // Get a ZipResourceFile representing a merger of both the main and patch files + if (expansionFile == null) { + String mainHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_MAIN_FILE_VERSION"); + if (mainHint == null) { + return null; // no expansion use if no main version was set + } + String patchHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_PATCH_FILE_VERSION"); + if (patchHint == null) { + return null; // no expansion use if no patch version was set + } + + Integer mainVersion; + Integer patchVersion; + try { + mainVersion = Integer.valueOf(mainHint); + patchVersion = Integer.valueOf(patchHint); + } catch (NumberFormatException ex) { + ex.printStackTrace(); + throw new IOException("No valid file versions set for APK expansion files", ex); + } + + try { + // To avoid direct dependency on Google APK expansion 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; + throw new IOException("Could not access APK expansion support library", ex); + } + } + + // 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) { + // calling "getInputStream" failed + ex.printStackTrace(); + throw new IOException("Could not open stream from APK expansion file", ex); + } + + if (fileStream == null) { + // calling "getInputStream" was successful but null was returned + throw new IOException("Could not find path in APK expansion file"); + } + + 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