diff --git a/.gitignore b/.gitignore
index 6f310b42..8a382b51 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,7 @@
-libs
-obj
-bin
-gen
-
-local.properties
-
+libs
+obj
+bin
+gen
+
+local.properties
+
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 3c31061d..b8ed3016 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1,51 +1,51 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/README.md b/README.md
index b247d2f7..8c1f4b33 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
-====
-Xash3D Android project
-====
-
-Just put into jni/src/XXXX/ a symlink to a repository. For example, for HLSDK it will be jni/src/HLSDK/halflife/
-
-Write here any issues related to Android port.
+====
+Xash3D Android project
+====
+
+Just put into jni/src/XXXX/ a symlink to a repository. For example, for HLSDK it will be jni/src/HLSDK/halflife/
+
+Write here any issues related to Android port.
diff --git a/ant.properties b/ant.properties
index b0971e89..a51e6a08 100644
--- a/ant.properties
+++ b/ant.properties
@@ -1,17 +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.
-
+# 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/assets/License.txt b/assets/License.txt
new file mode 100644
index 00000000..c9f1a93b
--- /dev/null
+++ b/assets/License.txt
@@ -0,0 +1,4 @@
+These PNG files are copyright to Emile Belanger.
+They are permitted for use in SDLxash3D Android port, full copyright is owned by Emile Belanger.
+Other use not permitted without permission from Emile Belanger.
+emile.belanger@gmail.com
\ No newline at end of file
diff --git a/assets/arrow_down.png b/assets/arrow_down.png
new file mode 100644
index 00000000..d353daa4
Binary files /dev/null and b/assets/arrow_down.png differ
diff --git a/assets/arrow_left.png b/assets/arrow_left.png
new file mode 100644
index 00000000..4328c08c
Binary files /dev/null and b/assets/arrow_left.png differ
diff --git a/assets/arrow_right.png b/assets/arrow_right.png
new file mode 100644
index 00000000..020be062
Binary files /dev/null and b/assets/arrow_right.png differ
diff --git a/assets/arrow_up.png b/assets/arrow_up.png
new file mode 100644
index 00000000..d94cfc27
Binary files /dev/null and b/assets/arrow_up.png differ
diff --git a/assets/crouch.png b/assets/crouch.png
new file mode 100644
index 00000000..18ad4b55
Binary files /dev/null and b/assets/crouch.png differ
diff --git a/assets/enter.png b/assets/enter.png
new file mode 100644
index 00000000..f74f327b
Binary files /dev/null and b/assets/enter.png differ
diff --git a/assets/flash_light_filled.png b/assets/flash_light_filled.png
new file mode 100644
index 00000000..d1954779
Binary files /dev/null and b/assets/flash_light_filled.png differ
diff --git a/assets/gamma.png b/assets/gamma.png
new file mode 100644
index 00000000..7a019f68
Binary files /dev/null and b/assets/gamma.png differ
diff --git a/assets/jump.png b/assets/jump.png
new file mode 100644
index 00000000..286d683d
Binary files /dev/null and b/assets/jump.png differ
diff --git a/assets/key_+.png b/assets/key_+.png
new file mode 100644
index 00000000..0eff7e65
Binary files /dev/null and b/assets/key_+.png differ
diff --git a/assets/key_-.png b/assets/key_-.png
new file mode 100644
index 00000000..e611ec65
Binary files /dev/null and b/assets/key_-.png differ
diff --git a/assets/key_0.png b/assets/key_0.png
new file mode 100644
index 00000000..a0f6d21c
Binary files /dev/null and b/assets/key_0.png differ
diff --git a/assets/key_1.png b/assets/key_1.png
new file mode 100644
index 00000000..42f22e83
Binary files /dev/null and b/assets/key_1.png differ
diff --git a/assets/key_2.png b/assets/key_2.png
new file mode 100644
index 00000000..28005d2d
Binary files /dev/null and b/assets/key_2.png differ
diff --git a/assets/key_3.png b/assets/key_3.png
new file mode 100644
index 00000000..37eac4c5
Binary files /dev/null and b/assets/key_3.png differ
diff --git a/assets/key_4.png b/assets/key_4.png
new file mode 100644
index 00000000..9a3ee73e
Binary files /dev/null and b/assets/key_4.png differ
diff --git a/assets/key_5.png b/assets/key_5.png
new file mode 100644
index 00000000..1a8abccd
Binary files /dev/null and b/assets/key_5.png differ
diff --git a/assets/key_6.png b/assets/key_6.png
new file mode 100644
index 00000000..87d5dee1
Binary files /dev/null and b/assets/key_6.png differ
diff --git a/assets/key_7.png b/assets/key_7.png
new file mode 100644
index 00000000..06cee9e6
Binary files /dev/null and b/assets/key_7.png differ
diff --git a/assets/key_8.png b/assets/key_8.png
new file mode 100644
index 00000000..445dba34
Binary files /dev/null and b/assets/key_8.png differ
diff --git a/assets/key_9.png b/assets/key_9.png
new file mode 100644
index 00000000..bce79c2a
Binary files /dev/null and b/assets/key_9.png differ
diff --git a/assets/key_f1.png b/assets/key_f1.png
new file mode 100644
index 00000000..664ad046
Binary files /dev/null and b/assets/key_f1.png differ
diff --git a/assets/key_f10.png b/assets/key_f10.png
new file mode 100644
index 00000000..6a15825a
Binary files /dev/null and b/assets/key_f10.png differ
diff --git a/assets/keyboard.png b/assets/keyboard.png
new file mode 100644
index 00000000..130755c4
Binary files /dev/null and b/assets/keyboard.png differ
diff --git a/assets/light.png b/assets/light.png
new file mode 100644
index 00000000..963a5c3b
Binary files /dev/null and b/assets/light.png differ
diff --git a/assets/load.png b/assets/load.png
new file mode 100644
index 00000000..95331ba9
Binary files /dev/null and b/assets/load.png differ
diff --git a/assets/look_arrow.png b/assets/look_arrow.png
new file mode 100644
index 00000000..878d054f
Binary files /dev/null and b/assets/look_arrow.png differ
diff --git a/assets/map.png b/assets/map.png
new file mode 100644
index 00000000..932fe530
Binary files /dev/null and b/assets/map.png differ
diff --git a/assets/next_weap.png b/assets/next_weap.png
new file mode 100644
index 00000000..3eb9acd2
Binary files /dev/null and b/assets/next_weap.png differ
diff --git a/assets/prev_weap.png b/assets/prev_weap.png
new file mode 100644
index 00000000..9e2fa321
Binary files /dev/null and b/assets/prev_weap.png differ
diff --git a/assets/red_cross.png b/assets/red_cross.png
new file mode 100644
index 00000000..4551068e
Binary files /dev/null and b/assets/red_cross.png differ
diff --git a/assets/reload.png b/assets/reload.png
new file mode 100644
index 00000000..9611e4cc
Binary files /dev/null and b/assets/reload.png differ
diff --git a/assets/save.png b/assets/save.png
new file mode 100644
index 00000000..c6d3df16
Binary files /dev/null and b/assets/save.png differ
diff --git a/assets/settings.png b/assets/settings.png
new file mode 100644
index 00000000..a66c5cd4
Binary files /dev/null and b/assets/settings.png differ
diff --git a/assets/settings_bars.png b/assets/settings_bars.png
new file mode 100644
index 00000000..463d5e4b
Binary files /dev/null and b/assets/settings_bars.png differ
diff --git a/assets/shoot.png b/assets/shoot.png
new file mode 100644
index 00000000..e00e45b4
Binary files /dev/null and b/assets/shoot.png differ
diff --git a/assets/show_weapons.png b/assets/show_weapons.png
new file mode 100644
index 00000000..58e2ed3e
Binary files /dev/null and b/assets/show_weapons.png differ
diff --git a/assets/strafe_arrow.png b/assets/strafe_arrow.png
new file mode 100644
index 00000000..9dae7d05
Binary files /dev/null and b/assets/strafe_arrow.png differ
diff --git a/assets/use.png b/assets/use.png
new file mode 100644
index 00000000..91acf891
Binary files /dev/null and b/assets/use.png differ
diff --git a/build.properties b/build.properties
index edc7f230..7886684f 100644
--- a/build.properties
+++ b/build.properties
@@ -1,17 +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.
-
+# 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/build.sh b/build.sh
index 70eb6c3f..680c168d 100755
--- a/build.sh
+++ b/build.sh
@@ -1,8 +1,8 @@
-#!/bin/sh
-
-ndk-build NDK_TOOLCHAIN_VERSION=4.8 NDK_DEBUG=0 SUPPORT_WEBP=false V=1
-ant release
-#jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ../myks.keystore bin/xashdroid-release-unsigned.apk xashdroid
-#adb install -r -f bin/xashdroid-debug.apk
-rm bin/xashdroid-release.apk
-/opt/android-sdk-update-manager/build-tools/19.1.0/zipalign 4 bin/xashdroid-release-unsigned.apk bin/xashdroid-release.apk
+#!/bin/sh
+
+ndk-build NDK_TOOLCHAIN_VERSION=4.8 NDK_DEBUG=0 SUPPORT_WEBP=false V=1
+ant release
+#jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ../myks.keystore bin/xashdroid-release-unsigned.apk xashdroid
+#adb install -r -f bin/xashdroid-debug.apk
+rm bin/xashdroid-release.apk
+/opt/android-sdk-update-manager/build-tools/19.1.0/zipalign 4 bin/xashdroid-release-unsigned.apk bin/xashdroid-release.apk
diff --git a/build.xml b/build.xml
index 96cb7ec6..de3e3f1e 100644
--- a/build.xml
+++ b/build.xml
@@ -1,92 +1,92 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/debug.sh b/debug.sh
index 4a895508..0db41f42 100755
--- a/debug.sh
+++ b/debug.sh
@@ -1,8 +1,8 @@
-#!/bin/sh
-
-ndk-build NDK_TOOLCHAIN_VERSION=4.8 NDK_DEBUG=1 SUPPORT_WEBP=false V=1 -j3 APP_CFLAGS="-gdwarf-3"
-ant debug
-#jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ../myks.keystore bin/xashdroid-release-unsigned.apk xashdroid -storepass 529459
-adb install -r -f bin/xashdroid-debug.apk
-#rm bin/xashdroid-release.apk
-#/opt/android-sdk-update-manager/build-tools/19.1.0/zipalign 4 bin/xashdroid-release-unsigned.apk bin/xashdroid-release.apk
+#!/bin/sh
+
+ndk-build NDK_TOOLCHAIN_VERSION=4.8 NDK_DEBUG=1 SUPPORT_WEBP=false V=1 -j3 APP_CFLAGS="-gdwarf-3"
+ant debug
+#jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ../myks.keystore bin/xashdroid-release-unsigned.apk xashdroid -storepass 529459
+adb install -r -f bin/xashdroid-debug.apk
+#rm bin/xashdroid-release.apk
+#/opt/android-sdk-update-manager/build-tools/19.1.0/zipalign 4 bin/xashdroid-release-unsigned.apk bin/xashdroid-release.apk
diff --git a/default.properties b/default.properties
index 0cdab956..0d39a1b6 100644
--- a/default.properties
+++ b/default.properties
@@ -1,11 +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
+# 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/jni/Android.mk b/jni/Android.mk
index 5053e7d6..7ae3545f 100644
--- a/jni/Android.mk
+++ b/jni/Android.mk
@@ -1 +1 @@
-include $(call all-subdir-makefiles)
+include $(call all-subdir-makefiles)
diff --git a/jni/Application.mk b/jni/Application.mk
index e481e89c..cd700236 100644
--- a/jni/Application.mk
+++ b/jni/Application.mk
@@ -1,21 +1,29 @@
-
-# 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
-
-APPLICATIONMK_PATH = $(call my-dir)
-
-SDL_PATH := $(APPLICATIONMK_PATH)/src/SDL2
-
-#SDL_IMAGE_PATH := $(APPLICATIONMK_PATH)/src/SDL2_image/
-
-NANOGL_PATH := $(APPLICATIONMK_PATH)/src/NanoGL/nanogl
-
-XASH3D_PATH := $(APPLICATIONMK_PATH)/src/Xash3D/xash3d
-
-XASHXT_PATH := $(APPLICATIONMK_PATH)/src/XashXT/XashXT
-
-HLSDK_PATH := $(APPLICATIONMK_PATH)/src/HLSDK/halflife/
-
-APP_ABI := armeabi-v7a x86
-APP_MODULES := SDL2 xash menu client server NanoGL
+
+# 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
+
+
+
+APPLICATIONMK_PATH = $(call my-dir)
+
+ifeq ($(EMILE),1)
+SDL_PATH := $(APPLICATIONMK_PATH)/src/SDL-mirror
+else
+SDL_PATH := $(APPLICATIONMK_PATH)/src/SDL2
+endif
+
+
+TOUCHCONTROLS_PATH := $(APPLICATIONMK_PATH)/src/MobileTouchControls
+
+
+NANOGL_PATH := $(APPLICATIONMK_PATH)/src/NanoGL/nanogl
+
+XASH3D_PATH := $(APPLICATIONMK_PATH)/src/Xash3D/xash3d
+
+XASHXT_PATH := $(APPLICATIONMK_PATH)/src/XashXT/XashXT
+
+HLSDK_PATH := $(APPLICATIONMK_PATH)/src/HLSDK/halflife/
+
+APP_ABI := armeabi-v7a x86
+APP_MODULES := SDL2 xash menu client server NanoGL
diff --git a/jni/src/Android.mk b/jni/src/Android.mk
index 5053e7d6..7ae3545f 100644
--- a/jni/src/Android.mk
+++ b/jni/src/Android.mk
@@ -1 +1 @@
-include $(call all-subdir-makefiles)
+include $(call all-subdir-makefiles)
diff --git a/jni/src/HLSDK/Android.mk b/jni/src/HLSDK/Android.mk
index 5053e7d6..7ae3545f 100644
--- a/jni/src/HLSDK/Android.mk
+++ b/jni/src/HLSDK/Android.mk
@@ -1 +1 @@
-include $(call all-subdir-makefiles)
+include $(call all-subdir-makefiles)
diff --git a/jni/src/HLSDK/halflife b/jni/src/HLSDK/halflife
deleted file mode 120000
index eb8bb8da..00000000
--- a/jni/src/HLSDK/halflife
+++ /dev/null
@@ -1 +0,0 @@
-/home/a1ba/projects/Xash3D/halflife
\ No newline at end of file
diff --git a/jni/src/HLSDK/halflife b/jni/src/HLSDK/halflife
new file mode 160000
index 00000000..c3b365fe
--- /dev/null
+++ b/jni/src/HLSDK/halflife
@@ -0,0 +1 @@
+Subproject commit c3b365fe0831e68199bb39e9f565ca724c7cc430
diff --git a/jni/src/NanoGL/Android.mk b/jni/src/NanoGL/Android.mk
index 5053e7d6..7ae3545f 100644
--- a/jni/src/NanoGL/Android.mk
+++ b/jni/src/NanoGL/Android.mk
@@ -1 +1 @@
-include $(call all-subdir-makefiles)
+include $(call all-subdir-makefiles)
diff --git a/jni/src/NanoGL/nanogl b/jni/src/NanoGL/nanogl
deleted file mode 120000
index 4476145c..00000000
--- a/jni/src/NanoGL/nanogl
+++ /dev/null
@@ -1 +0,0 @@
-/home/a1ba/projects/Xash3D/nanogl
\ No newline at end of file
diff --git a/jni/src/NanoGL/nanogl b/jni/src/NanoGL/nanogl
new file mode 160000
index 00000000..63045ea1
--- /dev/null
+++ b/jni/src/NanoGL/nanogl
@@ -0,0 +1 @@
+Subproject commit 63045ea16759232df455140efd8d5ed67986882d
diff --git a/jni/src/Xash3D/Android.mk b/jni/src/Xash3D/Android.mk
index 5053e7d6..7ae3545f 100644
--- a/jni/src/Xash3D/Android.mk
+++ b/jni/src/Xash3D/Android.mk
@@ -1 +1 @@
-include $(call all-subdir-makefiles)
+include $(call all-subdir-makefiles)
diff --git a/jni/src/Xash3D/xash3d b/jni/src/Xash3D/xash3d
deleted file mode 120000
index 0750ec38..00000000
--- a/jni/src/Xash3D/xash3d
+++ /dev/null
@@ -1 +0,0 @@
-/home/a1ba/projects/Xash3D/xash3d
\ No newline at end of file
diff --git a/jni/src/Xash3D/xash3d b/jni/src/Xash3D/xash3d
new file mode 160000
index 00000000..8e493fcd
--- /dev/null
+++ b/jni/src/Xash3D/xash3d
@@ -0,0 +1 @@
+Subproject commit 8e493fcd57ae8f2ab183a5e1d69580f0aa679740
diff --git a/jni/src/XashXT/Android.mk b/jni/src/XashXT/Android.mk
index 5053e7d6..7ae3545f 100644
--- a/jni/src/XashXT/Android.mk
+++ b/jni/src/XashXT/Android.mk
@@ -1 +1 @@
-include $(call all-subdir-makefiles)
+include $(call all-subdir-makefiles)
diff --git a/jni/src/XashXT/XashXT b/jni/src/XashXT/XashXT
deleted file mode 120000
index ed3bdbe2..00000000
--- a/jni/src/XashXT/XashXT
+++ /dev/null
@@ -1 +0,0 @@
-/home/a1ba/projects/Xash3D/XashXT
\ No newline at end of file
diff --git a/jni/src/XashXT/XashXT b/jni/src/XashXT/XashXT
new file mode 160000
index 00000000..222d4845
--- /dev/null
+++ b/jni/src/XashXT/XashXT
@@ -0,0 +1 @@
+Subproject commit 222d4845a6b0cf8926775bcf3c207ef0d31eb53b
diff --git a/libs/com.bda.controller.jar b/libs/com.bda.controller.jar
new file mode 100644
index 00000000..f71dbece
Binary files /dev/null and b/libs/com.bda.controller.jar differ
diff --git a/proguard-project.txt b/proguard-project.txt
index f2fe1559..b60ae7ea 100644
--- a/proguard-project.txt
+++ b/proguard-project.txt
@@ -1,20 +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 *;
-#}
+# 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/res/drawable/add.png b/res/drawable/add.png
new file mode 100644
index 00000000..3c5531d3
Binary files /dev/null and b/res/drawable/add.png differ
diff --git a/res/drawable/cog.png b/res/drawable/cog.png
new file mode 100644
index 00000000..18fe5183
Binary files /dev/null and b/res/drawable/cog.png differ
diff --git a/res/drawable/gamepad.png b/res/drawable/gamepad.png
new file mode 100644
index 00000000..9812a7ff
Binary files /dev/null and b/res/drawable/gamepad.png differ
diff --git a/res/drawable/gamepad_menu.png b/res/drawable/gamepad_menu.png
new file mode 100644
index 00000000..7943353e
Binary files /dev/null and b/res/drawable/gamepad_menu.png differ
diff --git a/res/drawable/help.png b/res/drawable/help.png
new file mode 100644
index 00000000..104a2c85
Binary files /dev/null and b/res/drawable/help.png differ
diff --git a/res/drawable/joystick.png b/res/drawable/joystick.png
new file mode 100644
index 00000000..cbfe03af
Binary files /dev/null and b/res/drawable/joystick.png differ
diff --git a/res/drawable/layout_sel_background.xml b/res/drawable/layout_sel_background.xml
new file mode 100644
index 00000000..ec60aefc
--- /dev/null
+++ b/res/drawable/layout_sel_background.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/controls_listview_item.xml b/res/layout/controls_listview_item.xml
new file mode 100644
index 00000000..ebe20a34
--- /dev/null
+++ b/res/layout/controls_listview_item.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/edit_controls_listview_item.xml b/res/layout/edit_controls_listview_item.xml
new file mode 100644
index 00000000..fbf3bfc2
--- /dev/null
+++ b/res/layout/edit_controls_listview_item.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/fragment_gamepad.xml b/res/layout/fragment_gamepad.xml
new file mode 100644
index 00000000..cee6ae9b
--- /dev/null
+++ b/res/layout/fragment_gamepad.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/main.xml b/res/layout/main.xml
index 123c4b6e..366c684d 100644
--- a/res/layout/main.xml
+++ b/res/layout/main.xml
@@ -1,13 +1,13 @@
-
-
-
-
-
+
+
+
+
+
diff --git a/res/layout/touch_controls_settings.xml b/res/layout/touch_controls_settings.xml
new file mode 100644
index 00000000..84abcd5f
--- /dev/null
+++ b/res/layout/touch_controls_settings.xml
@@ -0,0 +1,221 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 8a8dde5d..0a153033 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1,4 +1,12 @@
-
-
- Half-life
-
+
+
+ Half-life
+
+
+ - None
+ - Shoot
+ - Jump
+ - Use
+
+
+
diff --git a/src/com/beloko/games/hl/Game.java b/src/com/beloko/games/hl/Game.java
new file mode 100644
index 00000000..cb717c72
--- /dev/null
+++ b/src/com/beloko/games/hl/Game.java
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 2009 jeyries@yahoo.fr
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.beloko.games.hl;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+import org.libsdl.app.SDLActivity;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.Window;
+import android.view.WindowManager;
+
+import com.bda.controller.Controller;
+import com.bda.controller.ControllerListener;
+import com.bda.controller.StateEvent;
+import com.beloko.idtech.AppSettings;
+import com.beloko.idtech.BestEglChooser;
+import com.beloko.idtech.CDAudioPlayer;
+import com.beloko.idtech.MyGLSurfaceView;
+import com.beloko.idtech.Utils;
+import com.beloko.libsdl.SDLLib;
+import com.beloko.touchcontrols.FPSLimit;
+import com.beloko.touchcontrols.ControlInterpreter;
+import com.beloko.touchcontrols.Settings;
+import com.beloko.touchcontrols.QuakeCustomCommands;
+import com.beloko.touchcontrols.TouchControlsSettings;
+import com.beloko.touchcontrols.Settings.IDGame;
+
+public class Game extends Activity
+implements Handler.Callback
+{
+ String LOG = "Quake2";
+
+ private ControlInterpreter controlInterp;
+
+ private final MogaControllerListener mogaListener = new MogaControllerListener();
+ Controller mogaController = null;
+
+ private String args;
+ private String gamePath;
+
+ private GameView mGLSurfaceView = null;
+ private QuakeRenderer mRenderer = new QuakeRenderer();
+ Activity act;
+
+ int surfaceWidth,surfaceHeight;
+
+ int useGL;
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+
+ act = this;
+
+ AppSettings.setGame(IDGame.Wolf3d);
+ AppSettings.reloadSettings(getApplication());
+
+ args = getIntent().getStringExtra("args");
+ gamePath = getIntent().getStringExtra("game_path");
+ useGL = getIntent().getIntExtra("use_gl", 0);
+
+ handlerUI = new Handler(this);
+
+ mogaController = Controller.getInstance(this);
+ mogaController.init();
+ mogaController.setListener(mogaListener,new Handler());
+
+ //Log.i( "Quake2", "version : " + getVersion());
+
+ // fullscreen
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
+ WindowManager.LayoutParams.FLAG_FULLSCREEN);
+
+ // keep screen on
+ getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
+ WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+
+
+
+ start_quake2();
+
+ }
+
+
+ /// Handler for asynchronous message
+ /// => showDialog
+
+ private Handler handlerUI ;
+
+ public static final int MSG_SHOW_DIALOG = 1;
+
+
+ // implements Handler.Callback
+ @Override
+ public boolean handleMessage(Message msg) {
+
+ Log.i( "Quake2", String.format("handleMessage %d %d", msg.what, msg.arg1));
+
+ switch( msg.what ){
+
+ case MSG_SHOW_DIALOG:
+ showDialog(msg.arg1);
+ break;
+
+ }
+
+ return true;
+
+ }
+
+
+ /////////////////////////////
+
+
+
+
+ public void start_quake2() {
+
+ NativeLib.loadLibraries(useGL == 1);
+
+
+ NativeLib engine = new NativeLib();
+
+ NativeLib.loadLibraries(false);
+
+ controlInterp = new ControlInterpreter(engine,Settings.IDGame.Doom,AppSettings.gamePadControlsFile,AppSettings.gamePadEnabled);
+
+ TouchControlsSettings.setup(act, engine);
+ TouchControlsSettings.loadSettings(act);
+ TouchControlsSettings.sendToQuake();
+
+ QuakeCustomCommands.setup(act, engine,getIntent().getStringExtra("main_qc"),getIntent().getStringExtra("mod_qc"));
+
+
+ // Create our Preview view and set it as the content of our
+ // Activity
+ mGLSurfaceView = new GameView(this);
+
+ NativeLib.gv = mGLSurfaceView;
+
+ //if (renderType == NativeLib.REND_SOFT) //SDL software mode uses gles2
+ if (useGL == 1)
+ mGLSurfaceView.setEGLContextClientVersion(2); // enable OpenGL 2.0
+
+
+
+ //mGLSurfaceView.setGLWrapper( new MyWrapper());
+ //mGLSurfaceView.setDebugFlags(GLSurfaceView.DEBUG_CHECK_GL_ERROR | GLSurfaceView.DEBUG_LOG_GL_CALLS);
+ //setEGLConfigChooser (int redSize, int greenSize, int blueSize, int alphaSize, int depthSize, int stencilSize)
+ //mGLSurfaceView.setEGLConfigChooser(8,8,8,0,16,0);
+ mGLSurfaceView.setEGLConfigChooser( new BestEglChooser(getApplicationContext()) );
+
+ mGLSurfaceView.setRenderer(mRenderer);
+
+ // This will keep the screen on, while your view is visible.
+ mGLSurfaceView.setKeepScreenOn(true);
+
+ setContentView(mGLSurfaceView);
+ mGLSurfaceView.requestFocus();
+ mGLSurfaceView.setFocusableInTouchMode(true);
+
+ }
+
+
+
+ @Override
+ protected void onPause() {
+ Log.i( "Quake2.java", "onPause" );
+ CDAudioPlayer.onPause();
+ SDLLib.onPause();
+ mogaController.onPause();
+ super.onPause();
+ }
+
+ @Override
+ protected void onResume() {
+
+ Log.i( "Quake2.java", "onResume" );
+ CDAudioPlayer.onResume();
+ SDLLib.onResume();
+ mogaController.onResume();
+
+ super.onResume();
+ mGLSurfaceView.onResume();
+
+ }
+
+ @Override
+ protected void onDestroy() {
+ Log.i( "Quake2.java", "onDestroy" );
+ super.onDestroy();
+ mogaController.exit();
+ System.exit(0);
+ }
+
+ class MogaControllerListener implements ControllerListener {
+
+
+ @Override
+ public void onKeyEvent(com.bda.controller.KeyEvent event) {
+ //Log.d(LOG,"onKeyEvent " + event.getKeyCode());
+ controlInterp.onMogaKeyEvent(event,mogaController.getState(Controller.STATE_CURRENT_PRODUCT_VERSION));
+ }
+
+ @Override
+ public void onMotionEvent(com.bda.controller.MotionEvent event) {
+ // TODO Auto-generated method stub
+ Log.d(LOG,"onGenericMotionEvent " + event.toString());
+ controlInterp.onGenericMotionEvent(event);
+ }
+
+ @Override
+ public void onStateEvent(StateEvent event) {
+ Log.d(LOG,"onStateEvent " + event.getState());
+ }
+ }
+
+ class GameView extends MyGLSurfaceView {
+
+ /*--------------------
+ * Event handling
+ *--------------------*/
+
+
+ public GameView(Context context) {
+ super(context);
+
+ }
+
+ @Override
+ public boolean onGenericMotionEvent(MotionEvent event) {
+ return controlInterp.onGenericMotionEvent(event);
+ }
+ @Override
+ public boolean onTouchEvent(MotionEvent event)
+ {
+ return controlInterp.onTouchEvent(event);
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event)
+ {
+ return controlInterp.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event)
+ {
+ return controlInterp.onKeyUp(keyCode, event);
+ }
+
+ } // end of QuakeView
+
+
+
+
+ ///////////// GLSurfaceView.Renderer implementation ///////////
+
+ class QuakeRenderer implements MyGLSurfaceView.Renderer {
+
+
+
+ public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+ Log.d("Renderer", "onSurfaceCreated");
+ }
+
+
+ private void init( int width, int height ){
+
+ Log.i( "Quake2", "screen size : " + width + "x"+ height);
+
+ NativeLib.setScreenSize(width,height);
+
+ Utils.copyPNGAssets(getApplicationContext(),AppSettings.graphicsDir);
+
+ if (useGL == 0)
+ args = "--ren soft";
+
+ String[] args_array = Utils.creatArgs(args);
+
+ int ret = NativeLib.init(AppSettings.graphicsDir,64,args_array,useGL,gamePath);
+
+ Log.i("Quake2", "Quake2Init done");
+
+ }
+
+
+
+ //// new Renderer interface
+ int notifiedflags;
+
+ FPSLimit fpsLimit;
+
+ boolean inited = false;
+
+ public void onDrawFrame(GL10 gl) {
+
+ if (!inited)
+ {
+ SDLActivity.nativeInit();
+
+ AppSettings.setIntOption(getApplicationContext(), "max_fps", 0);
+
+ inited = true;
+ init( surfaceWidth, surfaceHeight );
+ }
+ }
+
+
+
+
+ public void onSurfaceChanged(GL10 gl, int width, int height) {
+
+
+ Log.d("Renderer", String.format("onSurfaceChanged %dx%d", width,height) );
+
+ // SDLLib.nativeInit(false);
+ // SDLLib.surfaceChanged(PixelFormat.RGBA_8888, width, height);
+
+ SDLActivity.onNativeResize(width, height,PixelFormat.RGBA_8888);
+
+ //SDLLib.surfaceChanged(PixelFormat.RGBA_8888, 320, 240);
+
+
+ controlInterp.setScreenSize(width, height);
+
+ surfaceWidth = width;
+ surfaceHeight = height;
+
+ }
+
+
+ } // end of QuakeRenderer
+
+
+}
+
+
diff --git a/src/com/beloko/games/hl/NativeLib.java b/src/com/beloko/games/hl/NativeLib.java
new file mode 100644
index 00000000..8690004b
--- /dev/null
+++ b/src/com/beloko/games/hl/NativeLib.java
@@ -0,0 +1,81 @@
+package com.beloko.games.hl;
+
+import com.beloko.touchcontrols.ControlInterface;
+
+public class NativeLib implements ControlInterface{
+
+
+ public static native int init(String graphics_dir,int mem,String[] args,int game,String path);
+ public static native int initTouchControls(String graphics_dir,int width,int height);
+
+ public static native void setScreenSize( int width, int height );
+
+ public static native boolean touchEvent( int action, int pid, float x, float y);
+ public static native void keypress(int down, int qkey, int unicode);
+ public static native void doAction(int state, int action);
+ public static native void analogFwd(float v);
+ public static native void analogSide(float v);
+ public static native void analogPitch(int mode,float v);
+ public static native void analogYaw(int mode,float v);
+ public static native void setTouchSettings(float alpha,float strafe,float fwd,float pitch,float yaw,int other);
+
+ public static native void quickCommand(String command);
+
+ @Override
+ public void initTouchControls_if(String pngPath,int width,int height) {
+ initTouchControls(pngPath,width,height);
+ }
+
+ @Override
+ public void quickCommand_if(String command){
+ quickCommand(command);
+ }
+
+ @Override
+ public boolean touchEvent_if(int action, int pid, float x, float y) {
+ return touchEvent( action, pid, x, y);
+ }
+ @Override
+ public void keyPress_if(int down, int qkey, int unicode) {
+ keypress(down,qkey,unicode);
+ }
+
+ @Override
+ public void doAction_if(int state, int action) {
+ doAction(state,action);
+ }
+
+ @Override
+ public void analogFwd_if(float v) {
+ analogFwd(v);
+ }
+ @Override
+ public void analogSide_if(float v) {
+ analogSide(v);
+ }
+ @Override
+ public void analogPitch_if(int mode,float v)
+ {
+ analogPitch(mode,v);
+ }
+ @Override
+ public void analogYaw_if(int mode,float v)
+ {
+ analogYaw(mode,v);
+ }
+
+ @Override
+ public void setTouchSettings_if(float alpha,float strafe, float fwd, float pitch,
+ float yaw, int other) {
+ setTouchSettings(alpha,strafe, fwd, pitch, yaw, other);
+
+ }
+
+ @Override
+ public int mapKey(int acode, int unicode) {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+
+}
diff --git a/src/com/beloko/touchcontrols/ActionInput.java b/src/com/beloko/touchcontrols/ActionInput.java
new file mode 100644
index 00000000..0b68a7fc
--- /dev/null
+++ b/src/com/beloko/touchcontrols/ActionInput.java
@@ -0,0 +1,37 @@
+package com.beloko.touchcontrols;
+
+import java.io.Serializable;
+
+import com.beloko.touchcontrols.ControlConfig.Type;
+
+
+public class ActionInput implements Serializable
+{
+ private static final long serialVersionUID = 1L;
+
+ public String tag;
+ public String description;
+ public boolean invert;
+ public float scale = 1; //senstivty for analog
+
+ public int source = -1;
+ public Type sourceType;
+ public boolean sourcePositive=true; //Used when using analog as a button
+
+ public int actionCode;
+ public Type actionType;
+
+ public ActionInput(String t,String n,int action,Type actiontype)
+ {
+ tag = t;
+ description = n;
+ actionCode = action;
+ actionType = actiontype;
+ }
+
+ public String toString()
+ {
+ return description + ":" + sourceType.toString() + " source: " + source + " sourcePositive: " + sourcePositive;
+ }
+}
+
diff --git a/src/com/beloko/touchcontrols/ControlConfig.java b/src/com/beloko/touchcontrols/ControlConfig.java
new file mode 100644
index 00000000..6338423d
--- /dev/null
+++ b/src/com/beloko/touchcontrols/ControlConfig.java
@@ -0,0 +1,699 @@
+package com.beloko.touchcontrols;
+
+import in.celest.xash3d.hl.R;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.util.ArrayList;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnDismissListener;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.CheckBox;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import com.beloko.touchcontrols.Settings.IDGame;
+
+public class ControlConfig implements Serializable{
+
+
+ final String LOG = "QuakeControlConfig";
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ enum Type {ANALOG,BUTTON,MENU};
+
+ public static final int LOOK_MODE_MOUSE =0;
+ public static final int LOOK_MODE_ABSOLUTE =1;
+ public static final int LOOK_MODE_JOYSTICK =2;
+
+ public static final int ACTION_ANALOG_FWD = 0x100;
+ public static final int ACTION_ANALOG_STRAFE = 0x101;
+ public static final int ACTION_ANALOG_PITCH = 0x102;
+ public static final int ACTION_ANALOG_YAW = 0x103;
+
+ public static final int PORT_ACT_LEFT =1;
+ public static final int PORT_ACT_RIGHT =2;
+ public static final int PORT_ACT_FWD =3;
+ public static final int PORT_ACT_BACK =4;
+ public static final int PORT_ACT_LOOK_UP =5;
+ public static final int PORT_ACT_LOOK_DOWN =6;
+ public static final int PORT_ACT_MOVE_LEFT =7;
+ public static final int PORT_ACT_MOVE_RIGHT =8;
+ public static final int PORT_ACT_STRAFE =9;
+ public static final int PORT_ACT_SPEED =10;
+ public static final int PORT_ACT_USE =11;
+ public static final int PORT_ACT_JUMP =12;
+ public static final int PORT_ACT_ATTACK =13;
+ public static final int PORT_ACT_UP =14;
+ public static final int PORT_ACT_DOWN =15;
+
+ public static final int PORT_ACT_NEXT_WEP =16;
+ public static final int PORT_ACT_PREV_WEP =17;
+
+ //Quake 2
+ public static final int PORT_ACT_INVEN = 18;
+ public static final int PORT_ACT_INVUSE = 19;
+ public static final int PORT_ACT_INVDROP = 20;
+ public static final int PORT_ACT_INVPREV = 21;
+ public static final int PORT_ACT_INVNEXT = 22;
+ public static final int PORT_ACT_HELPCOMP = 23;
+
+ //Doom
+ public static final int PORT_ACT_MAP = 30;
+ public static final int PORT_ACT_MAP_UP = 31;
+ public static final int PORT_ACT_MAP_DOWN = 32;
+ public static final int PORT_ACT_MAP_LEFT = 33;
+ public static final int PORT_ACT_MAP_RIGHT = 34;
+ public static final int PORT_ACT_MAP_ZOOM_IN = 35;
+ public static final int PORT_ACT_MAP_ZOOM_OUT = 36;
+
+ //RTCW
+ public static final int PORT_ACT_ZOOM_IN = 50;
+ public static final int PORT_ACT_ALT_FIRE = 51;
+ public static final int PORT_ACT_RELOAD = 52;
+ public static final int PORT_ACT_QUICKSAVE = 53;
+ public static final int PORT_ACT_QUICKLOAD = 54;
+ public static final int PORT_ACT_KICK = 56;
+ public static final int PORT_ACT_LEAN_LEFT = 57;
+ public static final int PORT_ACT_LEAN_RIGHT = 58;
+
+ //JK2
+ //public static final int PORT_ACT_FORCE_LIGHTNING = 60;
+ //public static final int PORT_ACT_SABER_BLOCK = 62;
+ //public static final int PORT_ACT_FORCE_GRIP = 63;
+ public static final int PORT_ACT_ALT_ATTACK = 64;
+ public static final int PORT_ACT_NEXT_FORCE = 65;
+ public static final int PORT_ACT_PREV_FORCE = 66;
+ public static final int PORT_ACT_FORCE_USE = 67;
+ public static final int PORT_ACT_DATAPAD = 68;
+ public static final int PORT_ACT_FORCE_SELECT = 69;
+ public static final int PORT_ACT_WEAPON_SELECT = 70;
+ public static final int PORT_ACT_SABER_STYLE = 71;
+ public static final int PORT_ACT_FORCE_PULL = 75;
+ public static final int PORT_ACT_FORCE_MIND = 76;
+ public static final int PORT_ACT_FORCE_LIGHT = 77;
+ public static final int PORT_ACT_FORCE_HEAL = 78;
+ public static final int PORT_ACT_FORCE_GRIP = 79;
+ public static final int PORT_ACT_FORCE_SPEED = 80;
+ public static final int PORT_ACT_FORCE_PUSH = 81;
+ public static final int PORT_ACT_SABER_SEL = 87; //Just chooses weapon 1 so show/hide saber.
+
+ //Choloate
+ public static final int PORT_ACT_GAMMA = 90;
+ public static final int PORT_ACT_SHOW_WEAPONS = 91;
+ public static final int PORT_ACT_SHOW_KEYS = 92;
+ public static final int PORT_ACT_FLY_UP = 93;
+ public static final int PORT_ACT_FLY_DOWN = 94;
+
+ //Custom
+ public static final int PORT_ACT_CUSTOM_0 = 150;
+ public static final int PORT_ACT_CUSTOM_1 = 151;
+ public static final int PORT_ACT_CUSTOM_2 = 152;
+ public static final int PORT_ACT_CUSTOM_3 = 153;
+ public static final int PORT_ACT_CUSTOM_4 = 154;
+ public static final int PORT_ACT_CUSTOM_5 = 155;
+ public static final int PORT_ACT_CUSTOM_6 = 156;
+ public static final int PORT_ACT_CUSTOM_7 = 157;
+
+
+
+ //Menu
+ public static final int MENU_UP = 0x200;
+ public static final int MENU_DOWN = 0x201;
+ public static final int MENU_LEFT = 0x202;
+ public static final int MENU_RIGHT = 0x203;
+ public static final int MENU_SELECT = 0x204;
+ public static final int MENU_BACK = 0x205;
+
+
+ Context ctx;
+ TextView infoTextView;
+
+ String filename;
+
+ boolean ignoreDirectionFromJoystick;
+
+ public ControlConfig(String file,IDGame game)
+ {
+ actions.add(new ActionInput("analog_move_fwd","Forward/Back",ACTION_ANALOG_FWD,Type.ANALOG));
+ actions.add(new ActionInput("analog_move_strafe","Strafe",ACTION_ANALOG_STRAFE,Type.ANALOG));
+
+
+ if (game != IDGame.Wolf3d)
+ actions.add(new ActionInput("analog_look_pitch","Look Up/Look Down",ACTION_ANALOG_PITCH,Type.ANALOG));
+
+ actions.add(new ActionInput("analog_look_yaw","Look Left/Look Right",ACTION_ANALOG_YAW,Type.ANALOG));
+
+ actions.add(new ActionInput("attack","Attack",PORT_ACT_ATTACK,Type.BUTTON));
+
+ if ((game == IDGame.Doom) || (game == IDGame.Wolf3d)|| (game == IDGame.Hexen)|| (game == IDGame.Strife)|| (game == IDGame.Heretic))
+ actions.add(new ActionInput("use","Use/Open",PORT_ACT_USE,Type.BUTTON));
+
+ if (game == IDGame.RTCW)
+ {
+ actions.add(new ActionInput("use","Use/Open",PORT_ACT_USE,Type.BUTTON));
+ actions.add(new ActionInput("reload","Reload",PORT_ACT_RELOAD,Type.BUTTON));
+ actions.add(new ActionInput("alt_fire","Alt Weapon",PORT_ACT_ALT_FIRE,Type.BUTTON));
+ actions.add(new ActionInput("binocular","Binocuar",PORT_ACT_ZOOM_IN,Type.BUTTON));
+ actions.add(new ActionInput("quick_kick","Kick",PORT_ACT_KICK,Type.BUTTON));
+ actions.add(new ActionInput("lean_left","Lean Left",PORT_ACT_LEAN_LEFT,Type.BUTTON));
+ actions.add(new ActionInput("lean_right","Lean Right",PORT_ACT_LEAN_RIGHT,Type.BUTTON));
+ }
+
+ if (game == IDGame.Quake3)
+ {
+ actions.add(new ActionInput("zoomin","Zoom in/out",PORT_ACT_ZOOM_IN,Type.BUTTON));
+ actions.add(new ActionInput("custom_0","Custom F1",PORT_ACT_CUSTOM_0,Type.BUTTON));
+ actions.add(new ActionInput("custom_1","Custom F2",PORT_ACT_CUSTOM_1,Type.BUTTON));
+ actions.add(new ActionInput("custom_2","Custom F3",PORT_ACT_CUSTOM_2,Type.BUTTON));
+ actions.add(new ActionInput("custom_3","Custom F4",PORT_ACT_CUSTOM_3,Type.BUTTON));
+ }
+
+ if ((game == IDGame.JK2) || (game == IDGame.JK3))
+ {
+ actions.add(new ActionInput("attack_alt","Alt Attack",PORT_ACT_ALT_ATTACK,Type.BUTTON));
+ actions.add(new ActionInput("use_force","Use Force",PORT_ACT_FORCE_USE,Type.BUTTON));
+ actions.add(new ActionInput("saber_style","Saber Style",PORT_ACT_SABER_STYLE,Type.BUTTON));
+ actions.add(new ActionInput("saber_show_hide","Saber Sheath/Unsheath",PORT_ACT_SABER_SEL,Type.BUTTON));
+ actions.add(new ActionInput("use","Use/Open",PORT_ACT_USE,Type.BUTTON));
+ }
+
+ if ((game != IDGame.Doom) && (game != IDGame.Wolf3d))
+ actions.add(new ActionInput("jump","Jump",PORT_ACT_JUMP,Type.BUTTON));
+
+ if ((game == IDGame.Quake2) || (game == IDGame.Quake3)|| (game == IDGame.Hexen2)|| (game == IDGame.RTCW)|| (game == IDGame.JK2) || (game == IDGame.JK3))
+ actions.add(new ActionInput("crouch","Crouch",PORT_ACT_DOWN,Type.BUTTON));
+
+ //Add GZDoom specific actions
+ if (game == IDGame.Doom)
+ {
+ actions.add(new ActionInput("attack_alt","Alt Attack (GZ)",PORT_ACT_ALT_ATTACK,Type.BUTTON));
+ actions.add(new ActionInput("jump","Jump (GZ)",PORT_ACT_JUMP,Type.BUTTON));
+ actions.add(new ActionInput("crouch","Crouch (GZ)",PORT_ACT_DOWN,Type.BUTTON));
+ actions.add(new ActionInput("custom_0","Custom A (GZ)",PORT_ACT_CUSTOM_0,Type.BUTTON));
+ actions.add(new ActionInput("custom_1","Custom B (GZ)",PORT_ACT_CUSTOM_1,Type.BUTTON));
+ actions.add(new ActionInput("custom_2","Custom C (GZ)",PORT_ACT_CUSTOM_2,Type.BUTTON));
+ actions.add(new ActionInput("custom_3","Custom D (GZ)",PORT_ACT_CUSTOM_3,Type.BUTTON));
+ actions.add(new ActionInput("custom_4","Custom E (GZ)",PORT_ACT_CUSTOM_4,Type.BUTTON));
+ actions.add(new ActionInput("custom_5","Custom F (GZ)",PORT_ACT_CUSTOM_5,Type.BUTTON));
+ actions.add(new ActionInput("quick_save","Quick Save (GZ)",PORT_ACT_QUICKSAVE,Type.BUTTON));
+ actions.add(new ActionInput("quick_load","Quick Load (GZ)",PORT_ACT_QUICKLOAD,Type.BUTTON));
+
+
+ }
+
+ actions.add(new ActionInput("fwd","Move Forward",PORT_ACT_FWD,Type.BUTTON));
+ actions.add(new ActionInput("back","Move Backwards",PORT_ACT_BACK,Type.BUTTON));
+ actions.add(new ActionInput("left","Strafe Left",PORT_ACT_MOVE_LEFT,Type.BUTTON));
+ actions.add(new ActionInput("right","Strafe Right",PORT_ACT_MOVE_RIGHT,Type.BUTTON));
+
+ if ((game != IDGame.Doom) && (game != IDGame.Wolf3d))
+ {
+ actions.add(new ActionInput("look_up","Look Up",PORT_ACT_LOOK_UP,Type.BUTTON));
+ actions.add(new ActionInput("look_down","Look Down",PORT_ACT_LOOK_DOWN,Type.BUTTON));
+ }
+
+ actions.add(new ActionInput("look_left","Look Left",PORT_ACT_LEFT,Type.BUTTON));
+ actions.add(new ActionInput("look_right","Look Right",PORT_ACT_RIGHT,Type.BUTTON));
+
+ if ((game != IDGame.Wolf3d) && (game != IDGame.JK2) || (game != IDGame.JK3))
+ {
+ actions.add(new ActionInput("strafe_on","Strafe On",PORT_ACT_STRAFE,Type.BUTTON));
+ actions.add(new ActionInput("speed","Run On",PORT_ACT_SPEED,Type.BUTTON));
+ }
+ actions.add(new ActionInput("next_weapon","Next Weapon",PORT_ACT_NEXT_WEP,Type.BUTTON));
+ actions.add(new ActionInput("prev_weapon","Previous Weapon",PORT_ACT_PREV_WEP,Type.BUTTON));
+
+ if ((game == IDGame.JK2)|| (game == IDGame.JK3))
+ {
+ actions.add(new ActionInput("next_force","Next Force",PORT_ACT_NEXT_FORCE,Type.BUTTON));
+ actions.add(new ActionInput("prev_force","Previous Force",PORT_ACT_PREV_FORCE,Type.BUTTON));
+ actions.add(new ActionInput("force_pull","Force Pull",PORT_ACT_FORCE_PULL,Type.BUTTON));
+ actions.add(new ActionInput("force_push","Force Push",PORT_ACT_FORCE_PUSH,Type.BUTTON));
+ actions.add(new ActionInput("force_speed","Force Speed",PORT_ACT_FORCE_SPEED,Type.BUTTON));
+ actions.add(new ActionInput("force_heal","Force Heal",PORT_ACT_FORCE_HEAL,Type.BUTTON));
+ actions.add(new ActionInput("force_mind","Force Mind",PORT_ACT_FORCE_MIND,Type.BUTTON));
+ actions.add(new ActionInput("force_grip","Force Grip",PORT_ACT_FORCE_GRIP,Type.BUTTON));
+ actions.add(new ActionInput("force_lightning","Force Lightning",PORT_ACT_FORCE_LIGHT,Type.BUTTON));
+
+ }
+
+ if ((game == IDGame.Quake2) || (game == IDGame.Hexen2)|| (game == IDGame.RTCW))
+ {
+ actions.add(new ActionInput("help_comp","Show Objectives",PORT_ACT_HELPCOMP,Type.BUTTON));
+ actions.add(new ActionInput("inv_show","Show Inventory",PORT_ACT_INVEN,Type.BUTTON));
+ actions.add(new ActionInput("inv_use","Use Item",PORT_ACT_INVUSE,Type.BUTTON));
+ actions.add(new ActionInput("inv_next","Next Item",PORT_ACT_INVNEXT,Type.BUTTON));
+ actions.add(new ActionInput("inv_prev","Prev Item",PORT_ACT_INVPREV,Type.BUTTON));
+ }
+
+ if (game == IDGame.JK2)
+ {
+ actions.add(new ActionInput("help_comp","Show Data Pad",PORT_ACT_DATAPAD,Type.BUTTON));
+ actions.add(new ActionInput("inv_use","Use Item",PORT_ACT_INVUSE,Type.BUTTON));
+ actions.add(new ActionInput("inv_next","Next Item",PORT_ACT_INVNEXT,Type.BUTTON));
+ actions.add(new ActionInput("inv_prev","Prev Item",PORT_ACT_INVPREV,Type.BUTTON));
+ }
+
+ if (game == IDGame.Hexen)
+ {
+ actions.add(new ActionInput("inv_use","Use Item",PORT_ACT_INVUSE,Type.BUTTON));
+ actions.add(new ActionInput("inv_next","Next Item",PORT_ACT_INVNEXT,Type.BUTTON));
+ actions.add(new ActionInput("inv_prev","Prev Item",PORT_ACT_INVPREV,Type.BUTTON));
+ actions.add(new ActionInput("fly_up","Fly Up",PORT_ACT_FLY_UP,Type.BUTTON));
+ actions.add(new ActionInput("fly_down","Fly Down",PORT_ACT_FLY_DOWN,Type.BUTTON));
+
+ }
+
+ if (game == IDGame.Strife)
+ {
+ actions.add(new ActionInput("inv_use","Use Item",PORT_ACT_INVUSE,Type.BUTTON));
+ actions.add(new ActionInput("inv_drop","Drop Item",PORT_ACT_INVDROP,Type.BUTTON));
+ actions.add(new ActionInput("inv_next","Next Item",PORT_ACT_INVNEXT,Type.BUTTON));
+ actions.add(new ActionInput("inv_prev","Prev Item",PORT_ACT_INVPREV,Type.BUTTON));
+ actions.add(new ActionInput("show_weap","Show Stats/Weapons",PORT_ACT_SHOW_WEAPONS,Type.BUTTON));
+ actions.add(new ActionInput("show_keys","Show Keys",PORT_ACT_SHOW_KEYS,Type.BUTTON));
+
+ }
+
+ if (game == IDGame.Heretic)
+ {
+ actions.add(new ActionInput("inv_use","Use Item",PORT_ACT_INVUSE,Type.BUTTON));
+ actions.add(new ActionInput("inv_next","Next Item",PORT_ACT_INVNEXT,Type.BUTTON));
+ actions.add(new ActionInput("inv_prev","Prev Item",PORT_ACT_INVPREV,Type.BUTTON));
+ actions.add(new ActionInput("fly_up","Fly Up",PORT_ACT_FLY_UP,Type.BUTTON));
+ actions.add(new ActionInput("fly_down","Fly Down",PORT_ACT_FLY_DOWN,Type.BUTTON));
+ }
+
+ if (game == IDGame.Quake3)
+ {
+ actions.add(new ActionInput("inv_use","Use Item",PORT_ACT_USE,Type.BUTTON));
+ }
+
+ if (game == IDGame.Doom)
+ {
+ actions.add(new ActionInput("map_show","Show Automap",PORT_ACT_MAP,Type.BUTTON));
+ actions.add(new ActionInput("map_up","Automap Up",PORT_ACT_MAP_UP,Type.BUTTON));
+ actions.add(new ActionInput("map_down","Automap Down",PORT_ACT_MAP_DOWN,Type.BUTTON));
+ actions.add(new ActionInput("map_left","Automap Left",PORT_ACT_MAP_LEFT,Type.BUTTON));
+ actions.add(new ActionInput("map_right","Automap Right",PORT_ACT_MAP_RIGHT,Type.BUTTON));
+ actions.add(new ActionInput("map_zoomin","Automap Zoomin",PORT_ACT_MAP_ZOOM_IN,Type.BUTTON));
+ actions.add(new ActionInput("map_zoomout","Automap Zoomout",PORT_ACT_MAP_ZOOM_OUT,Type.BUTTON));
+ }
+
+ if ((game == IDGame.RTCW) || (game == IDGame.JK2) || (game == IDGame.JK3))
+ {
+ actions.add(new ActionInput("quick_save","Quick Save",PORT_ACT_QUICKSAVE,Type.BUTTON));
+ actions.add(new ActionInput("quick_load","Quick Load",PORT_ACT_QUICKLOAD,Type.BUTTON));
+ }
+
+ if ((game == IDGame.Doom) || (game == IDGame.Heretic) || (game == IDGame.Hexen)
+ || (game == IDGame.Strife)|| (game == IDGame.Quake)|| (game == IDGame.Quake2)
+ || (game == IDGame.Hexen2)
+ || (game == IDGame.JK2) || (game == IDGame.JK3))
+ {
+ actions.add(new ActionInput("menu_up","Menu Up",MENU_UP,Type.MENU));
+ actions.add(new ActionInput("menu_down","Menu Down",MENU_DOWN,Type.MENU));
+ actions.add(new ActionInput("menu_left","Menu Left",MENU_LEFT,Type.MENU));
+ actions.add(new ActionInput("menu_right","Menu Right",MENU_RIGHT,Type.MENU));
+ actions.add(new ActionInput("menu_select","Menu Select",MENU_SELECT,Type.MENU));
+ actions.add(new ActionInput("menu_back","Menu Back",MENU_BACK,Type.MENU));
+ }
+ filename = file;
+ }
+
+ public void setTextView(Context c,TextView tv)
+ {
+ ctx = c;
+ infoTextView = tv;
+ }
+
+ void saveControls(File file) throws IOException
+ {
+ if (Settings.DEBUG) Log.d(LOG,"saveControls, file = " + file.toString());
+
+ FileOutputStream fos = null;
+ ObjectOutputStream out = null;
+
+ fos = new FileOutputStream(file);
+ out = new ObjectOutputStream(fos);
+ out.writeObject(actions);
+ out.close();
+ }
+
+ public void loadControls() throws IOException, ClassNotFoundException
+ {
+ loadControls(new File(filename));
+ }
+
+ public void loadControls(File file) throws IOException, ClassNotFoundException
+ {
+ if (Settings.DEBUG) Log.d(LOG,"loadControls, file = " + file.toString());
+
+ InputStream fis = null;
+ ObjectInputStream in = null;
+
+
+ fis = new FileInputStream(file);
+
+ in = new ObjectInputStream(fis);
+ ArrayList cd = (ArrayList )in.readObject();
+ if (Settings.DEBUG) Log.d(LOG,"loadControls, file loaded OK");
+ in.close();
+
+ for (ActionInput d: cd)
+ {
+ for (ActionInput a: actions)
+ {
+ if (d.tag.contentEquals(a.tag))
+ {
+ a.invert = d.invert;
+ a.source = d.source;
+ a.sourceType = d.sourceType;
+ a.sourcePositive = d.sourcePositive;
+ a.scale = d.scale;
+ if (a.scale == 0) a.scale = 1;
+ }
+ }
+ }
+
+ //Now check no buttons are also assigned to analog, if it is, clear the buttons
+ //This is because n00bs keep assigning movment analog AND buttons!
+ for (ActionInput a: actions)
+ {
+ if ((a.source != -1) && (a.sourceType == Type.ANALOG) && (a.actionType == Type.BUTTON))
+ {
+ for (ActionInput a_check: actions)
+ {
+ if ((a_check.sourceType == Type.ANALOG) && (a_check.actionType == Type.ANALOG))
+ {
+ if (a.source == a_check.source)
+ {
+ a.source = -1;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ fis.close();
+ }
+
+
+ void updated()
+ {
+ try {
+ saveControls(new File (filename));
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ ArrayList actions = new ArrayList();
+
+ ActionInput actionMontor=null;
+
+ boolean monitoring = false;
+
+ public boolean showExtraOptions(Activity act,int pos)
+ {
+ final ActionInput in = actions.get(pos);
+
+ if (in.actionType == Type.ANALOG)
+ {
+ Dialog dialog = new Dialog(act);
+ dialog.setTitle("Axis Sensitivity Setting");
+ dialog.setCancelable(true);
+
+ final LinearLayout l = new LinearLayout(act);
+ l.setOrientation(LinearLayout.VERTICAL);
+
+ final SeekBar sb = new SeekBar(act);
+ l.addView(sb);
+
+
+ sb.setMax(100);
+ sb.setProgress((int)(in.scale * 50));
+
+
+
+ final CheckBox invert = new CheckBox(act);
+ invert.setText("Invert");
+ invert.setChecked(in.invert);
+
+ l.addView(invert);
+
+ dialog.setOnDismissListener(new OnDismissListener() {
+
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ in.scale = (float)sb.getProgress()/(float)50;
+ in.invert = invert.isChecked();
+ updated();
+ }
+ });
+
+ dialog.setContentView(l);
+
+ dialog.show();
+ return true;
+ }
+ return false;
+ }
+
+ public void startMonitor(Activity act,int pos)
+ {
+ actionMontor = actions.get(pos);
+ monitoring = true;
+
+ if (actionMontor.actionType == Type.ANALOG)
+ infoTextView.setText("Move Stick for: " + actionMontor.description);
+ else
+ infoTextView.setText("Press Button for: " + actionMontor.description);
+
+ infoTextView.setTextColor(ctx.getResources().getColor(android.R.color.holo_green_light));
+ }
+
+ int[] axisTest = {
+ /*
+ MotionEvent.AXIS_GENERIC_1,
+ MotionEvent.AXIS_GENERIC_2,
+ MotionEvent.AXIS_GENERIC_3,
+ MotionEvent.AXIS_GENERIC_4,
+ MotionEvent.AXIS_GENERIC_5,
+ MotionEvent.AXIS_GENERIC_6,
+ MotionEvent.AXIS_GENERIC_7,
+ MotionEvent.AXIS_GENERIC_8,
+ MotionEvent.AXIS_GENERIC_9,
+ MotionEvent.AXIS_GENERIC_10,
+ MotionEvent.AXIS_GENERIC_11,
+ MotionEvent.AXIS_GENERIC_12,
+ MotionEvent.AXIS_GENERIC_13,
+ MotionEvent.AXIS_GENERIC_14,
+ MotionEvent.AXIS_GENERIC_15,
+ MotionEvent.AXIS_GENERIC_16,
+ */
+ MotionEvent.AXIS_HAT_X,
+ MotionEvent.AXIS_HAT_Y,
+ MotionEvent.AXIS_LTRIGGER,
+ MotionEvent.AXIS_RTRIGGER,
+ MotionEvent.AXIS_RUDDER,
+ MotionEvent.AXIS_RX,
+ MotionEvent.AXIS_RY,
+ MotionEvent.AXIS_RZ,
+ MotionEvent.AXIS_THROTTLE,
+ MotionEvent.AXIS_X,
+ MotionEvent.AXIS_Y,
+ MotionEvent.AXIS_Z,
+ MotionEvent.AXIS_BRAKE,
+ MotionEvent.AXIS_GAS,
+ };
+
+ public boolean onGenericMotionEvent(GenericAxisValues event)
+ {
+ Log.d(LOG,"onGenericMotionEvent");
+ if (monitoring)
+ {
+ if (actionMontor != null)
+ {
+ for (int a: axisTest)
+ {
+ if (Math.abs(event.getAxisValue(a)) > 0.6)
+ {
+ actionMontor.source = a;
+ actionMontor.sourceType = Type.ANALOG;
+ //Used for button actions
+ if (event.getAxisValue(a) > 0)
+ actionMontor.sourcePositive = true;
+ else
+ actionMontor.sourcePositive = false;
+
+ monitoring = false;
+
+ if (Settings.DEBUG) Log.d(LOG,actionMontor.description + " = Analog (" + actionMontor.source + ")");
+
+ infoTextView.setText("Select Action");
+ infoTextView.setTextColor(ctx.getResources().getColor(android.R.color.holo_blue_light));
+
+ updated();
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ public boolean isMonitoring()
+ {
+ return monitoring;
+ }
+
+ public boolean onKeyDown(int keyCode, KeyEvent event)
+ {
+ Log.d(LOG,"onKeyDown " + keyCode);
+
+ if (monitoring)
+ {
+ if (keyCode == KeyEvent.KEYCODE_BACK) //Cancel and clear button assignment
+ {
+ actionMontor.source = -1;
+ actionMontor.sourceType = Type.BUTTON;
+ monitoring = false;
+ infoTextView.setText("CANCELED");
+ infoTextView.setTextColor(ctx.getResources().getColor(android.R.color.holo_red_light));
+
+ updated();
+ return true;
+ }
+ else
+ {
+
+ if (actionMontor != null)
+ {
+ if (actionMontor.actionType != Type.ANALOG)
+ {
+ actionMontor.source = keyCode;
+ actionMontor.sourceType = Type.BUTTON;
+ monitoring = false;
+
+ infoTextView.setText("Select Action");
+ infoTextView.setTextColor(ctx.getResources().getColor(android.R.color.holo_blue_light));
+
+ updated();
+ return true;
+ }
+ }
+ }
+ }
+
+
+ return false;
+ }
+
+ public boolean onKeyUp(int keyCode, KeyEvent event)
+ {
+ return false;
+ }
+
+
+ public int getSize()
+ {
+ return actions.size();
+ }
+
+ public View getView(final Activity ctx,final int nbr)
+ {
+
+ View view = ctx.getLayoutInflater().inflate(R.layout.controls_listview_item, null);
+ ImageView image = (ImageView)view.findViewById(R.id.imageView);
+ TextView name = (TextView)view.findViewById(R.id.name_textview);
+ TextView binding = (TextView)view.findViewById(R.id.binding_textview);
+ ImageView setting_image = (ImageView)view.findViewById(R.id.settings_imageview);
+
+ ActionInput ai = actions.get(nbr);
+
+ if ((ai.actionType == Type.BUTTON) || (ai.actionType == Type.MENU))
+ {
+
+ if (ai.sourceType == Type.ANALOG)
+ binding.setText(MotionEvent.axisToString(ai.source));
+ else
+ binding.setText(KeyEvent.keyCodeToString(ai.source));
+
+ setting_image.setVisibility(View.GONE);
+
+ if ( (ai.actionType == Type.MENU))
+ {
+ name.setTextColor(0xFF00aeef); //BLUEY
+ image.setImageResource(R.drawable.gamepad_menu);
+ }
+ else
+ {
+ image.setImageResource(R.drawable.gamepad);
+ }
+ }
+ else if (ai.actionType == Type.ANALOG)
+ {
+ binding.setText(MotionEvent.axisToString(ai.source));
+ setting_image.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ showExtraOptions(ctx,nbr);
+ }
+ });
+ name.setTextColor(0xFFf7941d); //ORANGE
+ }
+
+ /*
+ if (ai.actionType == Type.BUTTON)
+ {
+ image.setImageResource(R.drawable.gamepad);
+ if (ai.sourceType == Type.ANALOG)
+ binding.setText(MotionEvent.axisToString(ai.source));
+ else
+ binding.setText(KeyEvent.keyCodeToString(ai.source));
+
+ setting_image.setVisibility(View.GONE);
+ }
+ else //Analog
+ {
+ binding.setText(MotionEvent.axisToString(ai.source));
+ setting_image.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ showExtraOptions(ctx,nbr);
+ }
+ });
+ name.setTextColor(0xFFf7941d);
+ }
+*/
+ name.setText(ai.description);
+
+ return view;
+ }
+}
diff --git a/src/com/beloko/touchcontrols/ControlInterface.java b/src/com/beloko/touchcontrols/ControlInterface.java
new file mode 100644
index 00000000..04e7881a
--- /dev/null
+++ b/src/com/beloko/touchcontrols/ControlInterface.java
@@ -0,0 +1,22 @@
+package com.beloko.touchcontrols;
+
+import android.view.KeyEvent;
+
+public interface ControlInterface {
+
+ public void initTouchControls_if(String pngPath,int width,int height);
+
+ public boolean touchEvent_if( int action, int pid, float x, float y);
+ public void keyPress_if(int down, int qkey, int unicode);
+ public void doAction_if(int state, int action);
+ public void analogFwd_if(float v);
+ public void analogSide_if(float v);
+ public void analogPitch_if(int mode,float v);
+ public void analogYaw_if(int mode,float v);
+ public void setTouchSettings_if(float alpha,float strafe,float fwd,float pitch,float yaw,int other);
+
+ public void quickCommand_if(String command);
+
+ public int mapKey(int acode,int unicode);
+}
+
\ No newline at end of file
diff --git a/src/com/beloko/touchcontrols/ControlInterpreter.java b/src/com/beloko/touchcontrols/ControlInterpreter.java
new file mode 100644
index 00000000..7b86400a
--- /dev/null
+++ b/src/com/beloko/touchcontrols/ControlInterpreter.java
@@ -0,0 +1,304 @@
+package com.beloko.touchcontrols;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+import com.bda.controller.Controller;
+import com.beloko.touchcontrols.ControlConfig.Type;
+import com.beloko.touchcontrols.Settings.IDGame;
+
+public class ControlInterpreter {
+
+ String LOG = "QuakeControlInterpreter";
+
+ ControlInterface quakeIf;
+ ControlConfig config;
+
+ boolean gamePadEnabled;
+
+ float screenWidth, screenHeight;
+
+ HashMap analogButtonState = new HashMap(); //Saves current state of analog buttons so all sent each time
+
+ public ControlInterpreter(ControlInterface qif,IDGame game,String controlfile,boolean ctrlEn)
+ {
+ Log.d("QuakeControlInterpreter", "file = " + controlfile);
+
+ gamePadEnabled = ctrlEn;
+
+ config = new ControlConfig(controlfile,game);
+ try {
+ config.loadControls();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ //e.printStackTrace();
+ } catch (ClassNotFoundException e) {
+ // TODO Auto-generated catch block
+ //e.printStackTrace();
+ }
+
+ for (ActionInput ai: config.actions)
+ {
+ if ((ai.sourceType == Type.ANALOG) && ((ai.actionType == Type.MENU) || (ai.actionType == Type.BUTTON)))
+ {
+ analogButtonState.put(ai.actionCode, false);
+ }
+ }
+
+ quakeIf = qif;
+ }
+
+ public void setScreenSize(int w,int h)
+ {
+ screenWidth = w;
+ screenHeight = h;
+ }
+
+ public boolean onTouchEvent(MotionEvent event)
+ {
+ int action = event.getAction();
+ int actionCode = action & MotionEvent.ACTION_MASK;
+
+ if (actionCode == MotionEvent.ACTION_MOVE)
+ {
+
+ for (int i = 0; i < event.getPointerCount(); i++) {
+
+ float x = event.getX(i)/screenWidth;
+ float y = event.getY(i)/screenHeight;
+ int pid = event.getPointerId(i);
+ quakeIf.touchEvent_if(3, pid, x, y);
+ }
+ }
+ else if (actionCode == MotionEvent.ACTION_DOWN)
+ {
+ float x = event.getX()/screenWidth;
+ float y = event.getY()/screenHeight;
+ quakeIf.touchEvent_if(1, 0, x, y);
+ }
+ else if (actionCode == MotionEvent.ACTION_POINTER_DOWN)
+ {
+ int index = event.getActionIndex();
+ if (index != -1)
+ {
+ float x = event.getX(index)/screenWidth;
+ float y = event.getY(index)/screenHeight;
+ int pid = event.getPointerId(index);
+ quakeIf.touchEvent_if(1, pid, x, y);
+ }
+ }
+ else if (actionCode == MotionEvent.ACTION_POINTER_UP)
+ {
+ int index = event.getActionIndex();
+ if (index != -1)
+ {
+
+ float x = event.getX(index)/screenWidth;
+ float y = event.getY(index)/screenHeight;
+ int pid = event.getPointerId(index);
+ quakeIf.touchEvent_if(2, pid, x, y);
+ }
+ }
+ else if (actionCode == MotionEvent.ACTION_UP)
+ {
+ float x = event.getX()/screenWidth;
+ float y = event.getY()/screenHeight;
+ int index = event.getActionIndex();
+ int pid = event.getPointerId(index);
+
+ quakeIf.touchEvent_if(2, pid, x, y);
+ }
+
+ return true;
+ }
+
+
+ public void onMogaKeyEvent(com.bda.controller.KeyEvent event,int pad_version)
+ {
+ int keycode = event.getKeyCode();
+
+ if (pad_version == Controller.ACTION_VERSION_MOGA)
+ {
+ //Log.d(LOG,"removed");
+ if ((keycode == com.bda.controller.KeyEvent.KEYCODE_DPAD_DOWN) ||
+ (keycode == com.bda.controller.KeyEvent.KEYCODE_DPAD_UP) ||
+ (keycode == com.bda.controller.KeyEvent.KEYCODE_DPAD_LEFT) ||
+ (keycode == com.bda.controller.KeyEvent.KEYCODE_DPAD_RIGHT))
+ return;
+ }
+
+ if (event.getAction() == com.bda.controller.KeyEvent.ACTION_DOWN)
+ onKeyDown(keycode, null);
+ else if (event.getAction() == com.bda.controller.KeyEvent.ACTION_UP)
+ onKeyUp(keycode, null);
+ }
+
+ public boolean onKeyDown(int keyCode, KeyEvent event)
+ {
+ boolean used = false;;
+ if (gamePadEnabled)
+ {
+ for (ActionInput ai: config.actions)
+ {
+ if (((ai.sourceType == Type.BUTTON)||(ai.sourceType == Type.MENU)) && (ai.source == keyCode))
+ {
+ quakeIf.doAction_if(1, ai.actionCode);
+ Log.d(LOG,"key down intercept");
+ used = true;
+ }
+ }
+ }
+
+ if (used)
+ return true;
+
+
+ if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP) || //If these were mapped it would have already returned
+ (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN))
+ return false;
+ else
+ {
+ int uc = 0;
+ if (event !=null)
+ uc = event.getUnicodeChar();
+ quakeIf.keyPress_if(1, quakeIf.mapKey(keyCode, uc), uc);
+ return true;
+ }
+ }
+
+ public boolean onKeyUp(int keyCode, KeyEvent event)
+ {
+ boolean used = false;
+
+ if (gamePadEnabled)
+ {
+ for (ActionInput ai: config.actions)
+ {
+ if (((ai.sourceType == Type.BUTTON) || (ai.sourceType == Type.MENU)) && (ai.source == keyCode))
+ {
+ quakeIf.doAction_if(0, ai.actionCode);
+ used = true;
+ }
+ }
+ }
+
+ if (used)
+ return true;
+
+ if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP) || //If these were mapped it would have already returned
+ (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN))
+ return false;
+ else
+ {
+ int uc = 0;
+ if (event !=null)
+ uc = event.getUnicodeChar();
+ quakeIf.keyPress_if(0, quakeIf.mapKey(keyCode, uc), uc);
+ return true;
+ }
+
+ }
+
+ float deadRegion = 0.2f;
+ private float analogCalibrate(float v)
+ {
+ if ((v < deadRegion) && (v > -deadRegion))
+ return 0;
+ else
+ {
+ if (v > 0)
+ return(v-deadRegion) / (1-deadRegion);
+ else
+ return(v+deadRegion) / (1-deadRegion);
+ //return v;
+ }
+ }
+
+ GenericAxisValues genericAxisValues = new GenericAxisValues();
+
+
+ //This is for normal Android motioon event
+ public boolean onGenericMotionEvent(MotionEvent event) {
+ genericAxisValues.setAndroidValues(event);
+ return onGenericMotionEvent(genericAxisValues);
+ }
+
+ //This is for Moga event
+ public boolean onGenericMotionEvent(com.bda.controller.MotionEvent event) {
+ genericAxisValues.setMogaValues(event);
+ return onGenericMotionEvent(genericAxisValues);
+ }
+
+ public boolean onGenericMotionEvent(GenericAxisValues event) {
+ if (Settings.DEBUG) Log.d(LOG,"onGenericMotionEvent" );
+
+ boolean used = false;
+ if (gamePadEnabled)
+ {
+ for (ActionInput ai: config.actions)
+ {
+ if ((ai.sourceType == Type.ANALOG) && (ai.source != -1))
+ {
+ int invert;
+ invert = ai.invert?-1:1;
+ if (ai.actionCode == ControlConfig.ACTION_ANALOG_PITCH)
+ quakeIf.analogPitch_if(ControlConfig.LOOK_MODE_JOYSTICK, analogCalibrate(event.getAxisValue(ai.source)) * invert * ai.scale);
+ else if (ai.actionCode == ControlConfig.ACTION_ANALOG_YAW)
+ quakeIf.analogYaw_if(ControlConfig.LOOK_MODE_JOYSTICK, -analogCalibrate(event.getAxisValue(ai.source)) * invert * ai.scale);
+ else if (ai.actionCode == ControlConfig.ACTION_ANALOG_FWD)
+ quakeIf.analogFwd_if(-analogCalibrate(event.getAxisValue(ai.source)) * invert * ai.scale);
+ else if (ai.actionCode == ControlConfig.ACTION_ANALOG_STRAFE)
+ quakeIf.analogSide_if(analogCalibrate(event.getAxisValue(ai.source)) * invert * ai.scale);
+ else //Must be using analog as a button
+ {
+ if (Settings.DEBUG) Log.d(LOG,"Analog as button" );
+
+ if (Settings.DEBUG) Log.d(LOG,ai.toString());
+
+ if (((ai.sourcePositive) && (event.getAxisValue(ai.source)) > 0.5) ||
+ ((!ai.sourcePositive) && (event.getAxisValue(ai.source)) < -0.5) )
+ {
+ if (!analogButtonState.get(ai.actionCode)) //Check internal state, only send if different
+ {
+ quakeIf.doAction_if(1, ai.actionCode); //press
+ analogButtonState.put(ai.actionCode, true);
+ }
+ }
+ else
+ {
+ if (analogButtonState.get(ai.actionCode)) //Check internal state, only send if different
+ {
+ quakeIf.doAction_if(0, ai.actionCode); //un-press
+ analogButtonState.put(ai.actionCode, false);
+ }
+ }
+
+ }
+ used = true;
+ }
+ /*
+ //Menu buttons
+ if ((ai.sourceType == Type.ANALOG) && (ai.actionType == Type.MENU) && (ai.source != -1))
+ {
+ if (GD.DEBUG) Log.d(LOG,"Analog as MENU button" );
+ if (GD.DEBUG) Log.d(LOG,ai.toString());
+ if (((ai.sourcePositive) && (event.getAxisValue(ai.source)) > 0.5) ||
+ ((!ai.sourcePositive) && (event.getAxisValue(ai.source)) < -0.5) )
+ quakeIf.doAction_if(1, ai.actionCode); //press
+ else
+ quakeIf.doAction_if(0, ai.actionCode); //un-press
+ }
+ */
+ }
+
+ }
+
+
+ return used;
+
+ }
+}
diff --git a/src/com/beloko/touchcontrols/GamePadFragment.java b/src/com/beloko/touchcontrols/GamePadFragment.java
new file mode 100644
index 00000000..f4fec401
--- /dev/null
+++ b/src/com/beloko/touchcontrols/GamePadFragment.java
@@ -0,0 +1,289 @@
+package com.beloko.touchcontrols;
+
+import in.celest.xash3d.hl.R;
+
+import java.io.IOException;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.AdapterView.OnItemLongClickListener;
+import android.widget.BaseAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.bda.controller.Controller;
+import com.bda.controller.ControllerListener;
+import com.bda.controller.StateEvent;
+
+public class GamePadFragment extends Fragment{
+ final String LOG = "GamePadFragment";
+
+ ListView listView;
+ ControlListAdapter adapter;
+
+ TextView info;
+
+ ControlConfig config;
+
+ GenericAxisValues genericAxisValues = new GenericAxisValues();
+
+ Controller mogaController = null;
+ final MogaControllerListener mListener = new MogaControllerListener();
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ config = new ControlConfig(Settings.gamePadControlsFile,Settings.game);
+
+ try {
+ config.loadControls();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ //e.printStackTrace();
+ } catch (ClassNotFoundException e) {
+ // TODO Auto-generated catch block
+ //e.printStackTrace();
+ }
+
+
+ mogaController = Controller.getInstance(getActivity());
+ mogaController.init();
+ mogaController.setListener(mListener,new Handler());
+ }
+
+
+ boolean isHidden = true;
+ @Override
+ public void onHiddenChanged(boolean hidden) {
+ isHidden = hidden;
+ super.onHiddenChanged(hidden);
+ }
+
+
+
+ @Override
+ public void onPause()
+ {
+ super.onPause();
+ mogaController.onPause();
+ }
+ @Override
+ public void onResume()
+ {
+ super.onResume();
+ mogaController.onResume();
+ }
+
+ @Override
+ public void onDestroy()
+ {
+ super.onDestroy();
+ mogaController.exit();
+ }
+
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View mainView = inflater.inflate(R.layout.fragment_gamepad, null);
+
+
+ CheckBox enableCb = (CheckBox)mainView.findViewById(R.id.gamepad_enable_checkbox);
+ enableCb.setChecked(Settings.gamePadEnabled);
+
+ enableCb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
+
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ Settings.setBoolOption(getActivity(), "gamepad_enabled", isChecked);
+ Settings.gamePadEnabled = isChecked;
+ setListViewEnabled(Settings.gamePadEnabled);
+
+ }
+ });
+
+
+ CheckBox hideCtrlCb = (CheckBox)mainView.findViewById(R.id.gamepad_hide_touch_checkbox);
+ hideCtrlCb.setChecked(Settings.hideTouchControls);
+
+ hideCtrlCb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
+
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ Settings.setBoolOption(getActivity(), "hide_touch_controls", isChecked);
+ Settings.hideTouchControls = isChecked;
+ }
+ });
+
+
+ Button help = (Button)mainView.findViewById(R.id.gamepad_help_button);
+ help.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ //NoticeDialog.show(getActivity(),"Gamepad Help", R.raw.gamepad);
+ }
+ });
+
+ listView = (ListView)mainView.findViewById(R.id.gamepad_listview);
+ adapter = new ControlListAdapter(getActivity());
+ listView.setAdapter(adapter);
+
+ setListViewEnabled(Settings.gamePadEnabled);
+
+
+ listView.setSelector(R.drawable.layout_sel_background);
+ listView.setOnItemClickListener(new OnItemClickListener() {
+
+ @Override
+ public void onItemClick(AdapterView> arg0, View v, int pos,
+ long id) {
+ config.startMonitor(getActivity(), pos);
+ }
+ });
+
+ listView.setOnItemLongClickListener(new OnItemLongClickListener() {
+
+ @Override
+ public boolean onItemLongClick(AdapterView> arg0, View v, int pos,
+ long id) {
+ return config.showExtraOptions(getActivity(), pos);
+ }
+ });
+
+ adapter.notifyDataSetChanged();
+
+ info = (TextView)mainView.findViewById(R.id.gamepad_info_textview);
+ info.setText("Select Action");
+ info.setTextColor(getResources().getColor(android.R.color.holo_blue_light));
+
+ config.setTextView(getActivity(),info);
+
+ return mainView;
+ }
+
+ private void setListViewEnabled(boolean v)
+ {
+
+ listView.setEnabled(v);
+ if (v)
+ {
+ listView.setAlpha(1);
+ }
+ else
+ {
+ listView.setAlpha(0.3f);
+ //listView.setBackgroundColor(Color.GRAY);
+ }
+ }
+
+ public boolean onGenericMotionEvent(MotionEvent event)
+ {
+ genericAxisValues.setAndroidValues(event);
+
+ if (config.onGenericMotionEvent(genericAxisValues))
+ adapter.notifyDataSetChanged();
+
+ //return config.isMonitoring(); //This does not work, mouse appears anyway
+ return !isHidden; //If gamepas tab visible always steal
+ }
+
+ public boolean onKeyDown(int keyCode, KeyEvent event)
+ {
+ if (config.onKeyDown(keyCode, event))
+ {
+ adapter.notifyDataSetChanged();
+ return true;
+ }
+ return false;
+ }
+
+ public boolean onKeyUp(int keyCode, KeyEvent event)
+ {
+ if(config.onKeyUp(keyCode, event))
+ {
+ adapter.notifyDataSetChanged();
+ return true;
+ }
+ return false;
+ }
+
+ class ControlListAdapter extends BaseAdapter{
+ private Activity context;
+
+ public ControlListAdapter(Activity context){
+ this.context=context;
+
+ }
+ public void add(String string){
+
+ }
+ public int getCount() {
+ return config.getSize();
+ }
+
+ public Object getItem(int arg0) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public long getItemId(int arg0) {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+
+ public View getView (int position, View convertView, ViewGroup list) {
+ View v = config.getView(getActivity(), position);
+ return v;
+ }
+
+ }
+
+
+ class MogaControllerListener implements ControllerListener {
+
+
+ @Override
+ public void onKeyEvent(com.bda.controller.KeyEvent event) {
+ //Log.d(LOG,"onKeyEvent " + event.getKeyCode());
+
+ if (event.getAction() == com.bda.controller.KeyEvent.ACTION_DOWN)
+ onKeyDown(event.getKeyCode(),null);
+ else if (event.getAction() == com.bda.controller.KeyEvent.ACTION_UP)
+ onKeyUp(event.getKeyCode(),null);
+ }
+
+ @Override
+ public void onMotionEvent(com.bda.controller.MotionEvent event) {
+ //Log.d(LOG,"onGenericMotionEvent " + event.toString());
+
+ genericAxisValues.setMogaValues(event);
+
+ if (config.onGenericMotionEvent(genericAxisValues))
+ adapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public void onStateEvent(StateEvent event) {
+ Log.d(LOG,"onStateEvent " + event.getState());
+ }
+ }
+
+}
diff --git a/src/com/beloko/touchcontrols/GenericAxisValues.java b/src/com/beloko/touchcontrols/GenericAxisValues.java
new file mode 100644
index 00000000..c568702a
--- /dev/null
+++ b/src/com/beloko/touchcontrols/GenericAxisValues.java
@@ -0,0 +1,29 @@
+package com.beloko.touchcontrols;
+
+import android.view.MotionEvent;
+
+public class GenericAxisValues {
+ float[] values = new float[64];
+
+ public float getAxisValue(int a)
+ {
+ return values[a];
+ }
+
+ public void setAxisValue(int a,float v)
+ {
+ values[a] = v;
+ }
+
+ public void setAndroidValues(MotionEvent event){
+ for (int n=0;n<64;n++)
+ values[n] = event.getAxisValue(n);
+ }
+
+ public void setMogaValues(com.bda.controller.MotionEvent event){
+ values[MotionEvent.AXIS_X] = event.getAxisValue(MotionEvent.AXIS_X);
+ values[MotionEvent.AXIS_Y] = event.getAxisValue(MotionEvent.AXIS_Y);
+ values[MotionEvent.AXIS_Z] = event.getAxisValue(MotionEvent.AXIS_Z);
+ values[MotionEvent.AXIS_RZ] = event.getAxisValue(MotionEvent.AXIS_RZ);
+ }
+}
diff --git a/src/com/beloko/touchcontrols/QuickCommand.java b/src/com/beloko/touchcontrols/QuickCommand.java
new file mode 100644
index 00000000..8328c3ff
--- /dev/null
+++ b/src/com/beloko/touchcontrols/QuickCommand.java
@@ -0,0 +1,36 @@
+package com.beloko.touchcontrols;
+
+import java.io.Serializable;
+
+public class QuickCommand implements Serializable{
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ String title;
+ String command;
+
+ QuickCommand(String title, String command)
+ {
+ this.title = title;
+ this.command = command;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getCommand() {
+ return command;
+ }
+
+ public void setCommand(String command) {
+ this.command = command;
+ }
+
+}
diff --git a/src/com/beloko/touchcontrols/Settings.java b/src/com/beloko/touchcontrols/Settings.java
new file mode 100644
index 00000000..6deeff37
--- /dev/null
+++ b/src/com/beloko/touchcontrols/Settings.java
@@ -0,0 +1,144 @@
+package com.beloko.touchcontrols;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.AssetManager;
+import android.util.Log;
+
+
+public class Settings {
+ public static boolean DEBUG = false;
+
+ public static String gamePadControlsFile = "gamepadSettings.dat";
+ public static String graphicsDir = "";
+
+ public static boolean gamePadEnabled;
+ public static boolean hideTouchControls;
+
+ public enum IDGame{Quake,Quake2,Doom,Duke3d,Quake3,Hexen2,RTCW,Wolf3d,JK2,JK3,Heretic,Hexen,Strife,AVP,Shadow,Gish,Descent1,Descent2,Homeworld,BlakeStone,Noah,Doom3};
+
+ public static IDGame game;
+
+
+ public static float getFloatOption(Context ctx,String name, float def)
+ {
+ SharedPreferences settings = ctx.getSharedPreferences("OPTIONS", Context.MODE_MULTI_PROCESS);
+ return settings.getFloat(name, def);
+ }
+
+ public static void setFloatOption(Context ctx,String name, float value)
+ {
+ SharedPreferences settings = ctx.getSharedPreferences("OPTIONS", Context.MODE_MULTI_PROCESS);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putFloat(name, value);
+ editor.commit();
+ }
+
+ public static boolean getBoolOption(Context ctx,String name, boolean def)
+ {
+ SharedPreferences settings = ctx.getSharedPreferences("OPTIONS", Context.MODE_MULTI_PROCESS);
+ return settings.getBoolean(name, def);
+ }
+
+ public static void setBoolOption(Context ctx,String name, boolean value)
+ {
+ SharedPreferences settings = ctx.getSharedPreferences("OPTIONS", Context.MODE_MULTI_PROCESS);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean(name, value);
+ editor.commit();
+ }
+
+ public static int getIntOption(Context ctx,String name, int def)
+ {
+ SharedPreferences settings = ctx.getSharedPreferences("OPTIONS", Context.MODE_MULTI_PROCESS);
+ return settings.getInt(name, def);
+ }
+
+ public static void setIntOption(Context ctx,String name, int value)
+ {
+ SharedPreferences settings = ctx.getSharedPreferences("OPTIONS", Context.MODE_MULTI_PROCESS);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putInt(name, value);
+ editor.commit();
+ }
+
+ public static long getLongOption(Context ctx,String name, long def)
+ {
+ SharedPreferences settings = ctx.getSharedPreferences("OPTIONS", Context.MODE_MULTI_PROCESS);
+ return settings.getLong(name, def);
+ }
+
+ public static void setLongOption(Context ctx,String name, long value)
+ {
+ SharedPreferences settings = ctx.getSharedPreferences("OPTIONS", Context.MODE_MULTI_PROCESS);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putLong(name, value);
+ editor.commit();
+ }
+
+ public static String getStringOption(Context ctx,String name, String def)
+ {
+ SharedPreferences settings = ctx.getSharedPreferences("OPTIONS", Context.MODE_MULTI_PROCESS);
+ return settings.getString(name, def);
+ }
+
+ public static void setStringOption(Context ctx,String name, String value)
+ {
+ SharedPreferences settings = ctx.getSharedPreferences("OPTIONS", Context.MODE_MULTI_PROCESS);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString(name, value);
+ editor.commit();
+ }
+
+ static public void copyFile(InputStream in, OutputStream out) throws IOException {
+ byte[] buffer = new byte[1024];
+ int read;
+ while((read = in.read(buffer)) != -1){
+ out.write(buffer, 0, read);
+ }
+ out.close();
+ }
+
+ static public void copyPNGAssets(Context ctx,String dir,String prefix) {
+
+ if (prefix == null)
+ prefix = "";
+
+ File d = new File(dir);
+ if (!d.exists())
+ d.mkdirs();
+
+ AssetManager assetManager = ctx.getAssets();
+ String[] files = null;
+ try {
+ files = assetManager.list("");
+ } catch (IOException e) {
+ Log.e("tag", "Failed to get asset file list.", e);
+ }
+ for(String filename : files) {
+ if (filename.endsWith("png") && filename.startsWith(prefix)){
+ InputStream in = null;
+ OutputStream out = null;
+ //Log.d("test","file = " + filename);
+ try {
+ in = assetManager.open(filename);
+ out = new FileOutputStream(dir + "/" + filename.substring(prefix.length()));
+ copyFile(in, out);
+ in.close();
+ in = null;
+ out.flush();
+ out.close();
+ out = null;
+ } catch(IOException e) {
+ Log.e("tag", "Failed to copy asset file: " + filename, e);
+ }
+ }
+ }
+ }
+}
diff --git a/src/com/beloko/touchcontrols/ShowKeyboard.java b/src/com/beloko/touchcontrols/ShowKeyboard.java
new file mode 100644
index 00000000..519d79e0
--- /dev/null
+++ b/src/com/beloko/touchcontrols/ShowKeyboard.java
@@ -0,0 +1,68 @@
+package com.beloko.touchcontrols;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.util.Log;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+
+public class ShowKeyboard {
+ static Activity activity;
+ static View view;;
+
+ public static void setup(Activity a,View v)
+ {
+ activity = a;
+ view = v;
+ }
+
+
+ public static void toggleKeyboard()
+ {
+ Log.d("ShowKeyboard","toggleKeyboard");
+
+ InputMethodManager im = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
+ if (im != null)
+ {
+ Log.d("ShowKeyboard","toggleKeyboard...");
+ im.toggleSoftInput(0, 0);
+ }
+ }
+
+ public static void showKeyboard(int show)
+ {
+ Log.d("ShowKeyboard","showKeyboard " + show);
+
+ InputMethodManager im = (InputMethodManager)activity.getSystemService(Context.INPUT_METHOD_SERVICE);
+ if (im != null)
+ {
+ if (show == 0)
+ {
+ im.hideSoftInputFromWindow(activity.getCurrentFocus().getWindowToken(), 0);
+ }
+ if (show == 1)
+ if (!im.isAcceptingText())
+ toggleKeyboard();
+ if (show == 2)
+ toggleKeyboard();
+ }
+
+ /*
+ InputMethodManager imm = (InputMethodManager)activity.getSystemService(Context.INPUT_METHOD_SERVICE);
+ if (show == 1)
+ imm.showSoftInput(view, InputMethodManager.SHOW_FORCED);
+ else
+ imm.hideSoftInputFromWindow(view.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
+ */
+ }
+
+ public static boolean hasHardwareKeyboard()
+ {
+ if(activity == null)
+ return false;
+
+ return activity.getApplicationContext().getResources().getConfiguration().keyboard == Configuration.KEYBOARD_QWERTY;
+ }
+
+}
diff --git a/src/com/beloko/touchcontrols/TouchControlsEditing.java b/src/com/beloko/touchcontrols/TouchControlsEditing.java
new file mode 100644
index 00000000..5e749568
--- /dev/null
+++ b/src/com/beloko/touchcontrols/TouchControlsEditing.java
@@ -0,0 +1,145 @@
+package com.beloko.touchcontrols;
+
+import in.celest.xash3d.hl.R;
+import android.app.Activity;
+import android.app.Dialog;
+import android.graphics.drawable.BitmapDrawable;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.BaseAdapter;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.ToggleButton;
+
+public class TouchControlsEditing {
+
+ static String TAG = "TouchControlsEditing";
+
+ static class ControlInfo
+ {
+ String tag;
+ String image;
+ boolean enabled;
+ boolean hidden;
+ }
+
+ static ListAdapter adapter;
+
+ static Activity activity;
+
+ public static native void JNIGetControlInfo(int pos,ControlInfo info);
+
+ public static native int JNIGetNbrControls();
+
+ public static native void JNISetHidden(int pos, boolean hidden);
+
+ public static void setup(Activity a)
+ {
+ activity = a;
+ }
+
+ public static void show()
+ {
+ show(activity);
+ }
+
+ public static void show(Activity act)
+ {
+ Log.d(TAG,"showSettings");
+
+ if (act != null)
+ activity = act;
+
+ activity.runOnUiThread(new Runnable(){
+ public void run() {
+ final Dialog dialog = new Dialog(activity);
+ ListView listView = new ListView(activity);
+
+ dialog.setContentView(listView);
+ dialog.setTitle("Add/remove buttons");
+ dialog.setCancelable(true);
+
+ adapter = new ListAdapter(activity);
+ listView.setAdapter(adapter);
+
+ dialog.getWindow().setFlags(
+ WindowManager.LayoutParams.FLAG_FULLSCREEN,
+ WindowManager.LayoutParams.FLAG_FULLSCREEN);
+
+ dialog.show();
+ }
+ });
+
+ }
+
+ static class ListAdapter extends BaseAdapter{
+ private Activity context;
+
+ public ListAdapter(Activity context){
+ this.context=context;
+
+ }
+ public void add(String string){
+
+ }
+ public int getCount() {
+ return TouchControlsEditing.JNIGetNbrControls();
+ }
+
+ public Object getItem(int arg0) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public long getItemId(int arg0) {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+
+ public View getView (int position, View convertView, ViewGroup list) {
+ //if (convertView == null) dont reuse view otherwise check change get called
+ convertView = activity.getLayoutInflater().inflate(R.layout.edit_controls_listview_item, null);
+
+ final int my_pos = position;
+
+ ImageView image = (ImageView)convertView.findViewById(R.id.imageView);
+ TextView name = (TextView)convertView.findViewById(R.id.name_textview);
+ ToggleButton hidden = (ToggleButton)convertView.findViewById(R.id.hidden_switch);
+
+
+ TouchControlsEditing.ControlInfo ci = new TouchControlsEditing.ControlInfo();
+ TouchControlsEditing.JNIGetControlInfo(position, ci);
+
+ name.setText(ci.tag);
+ hidden.setChecked(!ci.hidden);
+ hidden.setTag(new Integer(position));
+
+ hidden.setOnCheckedChangeListener(new OnCheckedChangeListener() {
+
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ Integer pos = (Integer)buttonView.getTag();
+
+ TouchControlsEditing.JNISetHidden(pos, !isChecked);
+ adapter.notifyDataSetChanged();
+ }
+ });
+
+ String png = activity.getFilesDir() + "/" + ci.image + ".png";
+ Log.d(TAG,"png = " + png);
+ BitmapDrawable bm = new BitmapDrawable(png);
+
+ image.setImageDrawable(bm);
+ return convertView;
+ }
+
+ }
+
+
+}
diff --git a/src/com/beloko/touchcontrols/TouchControlsSettings.java b/src/com/beloko/touchcontrols/TouchControlsSettings.java
new file mode 100644
index 00000000..4cc46062
--- /dev/null
+++ b/src/com/beloko/touchcontrols/TouchControlsSettings.java
@@ -0,0 +1,265 @@
+package com.beloko.touchcontrols;
+
+
+import in.celest.xash3d.hl.R;
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnDismissListener;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.SeekBar;
+import android.widget.Spinner;
+
+
+public class TouchControlsSettings {
+
+ static Activity activity;
+ static ControlInterface quakeIf;
+
+
+ static int alpha,fwdSens,strafeSens,pitchSens,yawSens;
+
+ static boolean mouseMode,showWeaponCycle,showSticks,enableWeaponWheel;
+ static boolean invertLook,precisionShoot;
+
+ static int doubleTapMove,doubleTapLook;
+
+ public static void setup(Activity a,ControlInterface qif)
+ {
+ activity = a;
+ quakeIf = qif;
+ }
+
+ public static void showSettings()
+ {
+ Log.d("settings","showSettings");
+
+ activity.runOnUiThread(new Runnable(){
+ public void run() {
+ final Dialog dialog = new Dialog(activity);
+ dialog.setContentView(R.layout.touch_controls_settings);
+ dialog.setTitle("Touch Control Sensitivity Settings");
+ dialog.setCancelable(true);
+
+ final SeekBar alphaSeek = (SeekBar)dialog.findViewById(R.id.alpha_seekbar);
+ final SeekBar fwdSeek = (SeekBar)dialog.findViewById(R.id.fwd_seekbar);
+ final SeekBar strafeSeek = (SeekBar)dialog.findViewById(R.id.strafe_seekbar);
+ final SeekBar pitchSeek = (SeekBar)dialog.findViewById(R.id.pitch_seekbar);
+ final SeekBar yawSeek = (SeekBar)dialog.findViewById(R.id.yaw_seekbar);
+
+ final CheckBox mouseModeCheck = (CheckBox)dialog.findViewById(R.id.mouse_turn_checkbox);
+ final CheckBox showWeaponCycleCheckBox = (CheckBox)dialog.findViewById(R.id.show_next_weapon_checkbox);
+ final CheckBox invertLookCheckBox = (CheckBox)dialog.findViewById(R.id.invert_loop_checkbox);
+ final CheckBox precisionShootCheckBox = (CheckBox)dialog.findViewById(R.id.precision_shoot_checkbox);
+ final CheckBox showSticksCheckBox = (CheckBox)dialog.findViewById(R.id.show_sticks_checkbox);
+ final CheckBox enableWeaponWheelCheckBox = (CheckBox)dialog.findViewById(R.id.enable_weapon_wheel_checkbox);
+
+ /*
+ //Hide controls for lookup/down
+ if (Settings.game == IDGame.Doom)
+ {
+ //pitchSeek.setVisibility(View.GONE);
+ invertLookCheckBox.setVisibility(View.GONE);
+ }
+ */
+ Button add_rem_button = (Button)dialog.findViewById(R.id.add_remove_button);
+ add_rem_button.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ TouchControlsEditing.show(activity);
+ }
+ });
+
+ alphaSeek.setProgress(alpha);
+ fwdSeek.setProgress(fwdSens);
+ strafeSeek.setProgress(strafeSens);
+ pitchSeek.setProgress(pitchSens);
+ yawSeek.setProgress(yawSens);
+
+ mouseModeCheck.setChecked(mouseMode);
+ showWeaponCycleCheckBox.setChecked(showWeaponCycle);
+ invertLookCheckBox.setChecked(invertLook);
+ precisionShootCheckBox.setChecked(precisionShoot);
+ showSticksCheckBox.setChecked(showSticks);
+ enableWeaponWheelCheckBox.setChecked(enableWeaponWheel);
+
+ Spinner move_spinner = (Spinner) dialog.findViewById(R.id.move_dbl_tap_spinner);
+ ArrayAdapter adapterm;
+
+ adapterm = ArrayAdapter.createFromResource(activity,
+ R.array.double_tap_actions, android.R.layout.simple_spinner_item);
+
+ adapterm.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ move_spinner.setAdapter(adapterm);
+ move_spinner.setSelection(doubleTapMove);
+
+ move_spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView> parent, View view,
+ int pos, long id) {
+ doubleTapMove = pos;
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView> arg0) {
+ // TODO Auto-generated method stub
+
+ }
+ });
+
+ Spinner look_spinner = (Spinner) dialog.findViewById(R.id.look_dbl_tap_spinner);
+ ArrayAdapter adapterl = ArrayAdapter.createFromResource(activity,
+ R.array.double_tap_actions, android.R.layout.simple_spinner_item);
+ adapterl.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ look_spinner.setAdapter(adapterl);
+
+ look_spinner.setSelection(doubleTapLook);
+
+ look_spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView> parent, View view,
+ int pos, long id) {
+ doubleTapLook = pos;
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView> arg0) {
+ // TODO Auto-generated method stub
+
+ }
+ });
+
+ dialog.setOnDismissListener(new OnDismissListener() {
+
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ alpha = alphaSeek.getProgress();
+ fwdSens = fwdSeek.getProgress();
+ strafeSens = strafeSeek.getProgress();
+ pitchSens = pitchSeek.getProgress();
+ yawSens = yawSeek.getProgress();
+
+ mouseMode = mouseModeCheck.isChecked();
+ showWeaponCycle = showWeaponCycleCheckBox.isChecked();
+ invertLook = invertLookCheckBox.isChecked();
+ precisionShoot = precisionShootCheckBox.isChecked();
+ showSticks = showSticksCheckBox.isChecked();
+ enableWeaponWheel = enableWeaponWheelCheckBox.isChecked();
+
+ saveSettings(activity);
+ sendToQuake();
+ }
+ });
+
+ Button save = (Button)dialog.findViewById(R.id.save_button);
+ save.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ alpha = alphaSeek.getProgress();
+ fwdSens = fwdSeek.getProgress();
+ strafeSens = strafeSeek.getProgress();
+ pitchSens = pitchSeek.getProgress();
+ yawSens = yawSeek.getProgress();
+
+ mouseMode = mouseModeCheck.isChecked();
+ showWeaponCycle = showWeaponCycleCheckBox.isChecked();
+ invertLook = invertLookCheckBox.isChecked();
+ precisionShoot = precisionShootCheckBox.isChecked();
+ showSticks = showSticksCheckBox.isChecked();
+ enableWeaponWheel = enableWeaponWheelCheckBox.isChecked();
+
+ saveSettings(activity);
+ sendToQuake();
+ dialog.dismiss();
+ }
+ });
+
+ Button cancel = (Button)dialog.findViewById(R.id.cancel_button);
+ cancel.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ dialog.dismiss();
+ }
+ });
+
+ dialog.show();
+ }
+ });
+
+ }
+
+
+
+ public static void sendToQuake()
+ {
+
+ int other = 0;
+ other += showWeaponCycle?0x1:0;
+ other += mouseMode?0x2:0;
+ other += invertLook?0x4:0;
+ other += precisionShoot?0x8:0;
+
+ other += (doubleTapMove << 4) & 0xF0;
+ other += (doubleTapLook << 8) & 0xF00;
+
+ other += showSticks?0x1000:0;
+ other += enableWeaponWheel?0x2000:0;
+
+ other += Settings.hideTouchControls?0x80000000:0;
+
+ quakeIf.setTouchSettings_if(
+ (float)alpha/(float)100,
+ (strafeSens)/(float)50,
+ (fwdSens)/(float)50,
+ (pitchSens)/(float)50,
+ (yawSens)/(float)50,
+ other);
+ }
+
+ public static void loadSettings(Context ctx)
+ {
+ alpha = Settings.getIntOption(ctx, "alpha", 50);
+ fwdSens = Settings.getIntOption(ctx, "fwdSens", 50);
+ strafeSens = Settings.getIntOption(ctx, "strafeSens", 50);
+ pitchSens = Settings.getIntOption(ctx, "pitchSens", 50);
+ yawSens = Settings.getIntOption(ctx, "yawSens", 50);
+
+ showWeaponCycle = Settings.getBoolOption(ctx, "show_weapon_cycle", true);
+ mouseMode = Settings.getBoolOption(ctx, "mouse_mode", true);
+ invertLook = Settings.getBoolOption(ctx, "invert_look", false);
+ precisionShoot = Settings.getBoolOption(ctx, "precision_shoot", false);
+ showSticks = Settings.getBoolOption(ctx, "show_sticks", false);
+ enableWeaponWheel = Settings.getBoolOption(ctx, "enable_ww", true);
+
+ doubleTapMove = Settings.getIntOption(ctx, "double_tap_move", 0);
+ doubleTapLook = Settings.getIntOption(ctx, "double_tap_look", 0);
+ }
+
+ public static void saveSettings(Context ctx)
+ {
+ Settings.setIntOption(ctx, "alpha", alpha);
+ Settings.setIntOption(ctx, "fwdSens", fwdSens);
+ Settings.setIntOption(ctx, "strafeSens", strafeSens);
+ Settings.setIntOption(ctx, "pitchSens", pitchSens);
+ Settings.setIntOption(ctx, "yawSens", yawSens);
+
+ Settings.setBoolOption(ctx, "show_weapon_cycle", showWeaponCycle);
+ Settings.setBoolOption(ctx, "invert_look", invertLook);
+ Settings.setBoolOption(ctx, "precision_shoot", precisionShoot);
+ Settings.setBoolOption(ctx, "show_sticks", showSticks);
+ Settings.setBoolOption(ctx, "enable_ww", enableWeaponWheel);
+
+ Settings.setIntOption(ctx, "double_tap_move", doubleTapMove);
+ Settings.setIntOption(ctx, "double_tap_look", doubleTapLook);
+
+ }
+}
diff --git a/src/org/libsdl/app/SDLActivity.java b/src/org/libsdl/app/SDLActivity.java
index 61a34c5a..b6665cb5 100644
--- a/src/org/libsdl/app/SDLActivity.java
+++ b/src/org/libsdl/app/SDLActivity.java
@@ -1,1066 +1,1629 @@
-
-package org.libsdl.app;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
-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.os.*;
-import android.util.Log;
-import android.graphics.*;
-import android.media.*;
-import android.hardware.*;
-
-
-/**
- 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 {
-
- System.loadLibrary("SDL2");
- //System.loadLibrary("SDL2_image");
- //System.loadLibrary("SDL2_mixer");
- //System.loadLibrary("SDL2_net");
- //System.loadLibrary("SDL2_ttf");
- System.loadLibrary("xash");
- }
-
-
- 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 onWindowFocusChanged(boolean hasFocus) {
- super.onWindowFocusChanged(hasFocus);
- Log.v("SDL", "onWindowFocusChanged(): " + hasFocus);
-
- SDLActivity.mHasFocus = hasFocus;
- if (hasFocus) {
- 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_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.enableSensor(Sensor.TYPE_ACCELEROMETER, true);
- }
- }
-
- /* The native thread has finished */
- public static void handleNativeExit() {
- SDLActivity.mSDLThread = null;
- mSingleton.finish();
- System.exit(0);
- }
-
-
- // 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;
-
- 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;
-
- 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 void nativeInit();
- 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 void flipBuffers() {
- SDLActivity.nativeFlipBuffers();
- }
-
- public static boolean setActivityTitle(String title) {
- // Called from SDLMain() thread and can't directly affect the view
- return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title);
- }
-
- public static boolean sendMessage(int command, int param) {
- return mSingleton.sendCommand(command, Integer.valueOf(param));
- }
-
- public static Context getContext() {
- return mSingleton;
- }
-
- /**
- * @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);
- }
- }
-
- 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));
- }
-
- public static Surface getNativeSurface() {
- return SDLActivity.mSurface.getNativeSurface();
- }
-
- // Audio
- 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;
- }
-
- 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;
- }
- }
- }
-
- 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;
- }
- }
- }
-
- public static void audioQuit() {
- if (mAudioTrack != null) {
- mAudioTrack.stop();
- mAudioTrack = null;
- }
- }
-
- // Input
-
- /**
- * @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);
- }
-
- public static void pollInputDevices() {
- if (SDLActivity.mSDLThread != null) {
- mJoystickHandler.pollInputDevices();
- }
- }
-
-}
-
-/**
- Simple nativeInit() runnable
-*/
-class SDLMain implements Runnable {
- @Override
- public void run() {
- // Runs SDL_main()
- SDLActivity.nativeInit();
-
- //Log.v("SDL", "SDL thread terminated");
- }
-}
-
-
-/**
- SDLSurface. This is what we draw on, so we need to know when it's created
- in order to do anything useful.
-
- Because of this, that's where we set up the SDL thread
-*/
-class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
- View.OnKeyListener, View.OnTouchListener, SensorEventListener {
-
- // Sensors
- protected static SensorManager mSensorManager;
- protected static Display mDisplay;
-
- // Keep track of the surface size to normalize touch events
- protected static float mWidth, mHeight;
-
- // Startup
- public SDLSurface(Context context) {
- super(context);
- getHolder().addCallback(this);
-
- setFocusable(true);
- setFocusableInTouchMode(true);
- requestFocus();
- setOnKeyListener(this);
- setOnTouchListener(this);
-
- mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
- mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
-
- if(Build.VERSION.SDK_INT >= 12) {
- setOnGenericMotionListener(new SDLGenericMotionListener_API12());
- }
-
- // Some arbitrary defaults to avoid a potential division by zero
- mWidth = 1.0f;
- mHeight = 1.0f;
- }
-
- public Surface getNativeSurface() {
- return getHolder().getSurface();
- }
-
- // Called when we have a valid drawing surface
- @Override
- public void surfaceCreated(SurfaceHolder holder) {
- Log.v("SDL", "surfaceCreated()");
- holder.setType(SurfaceHolder.SURFACE_TYPE_GPU);
- }
-
- // Called when we lose the surface
- @Override
- public void surfaceDestroyed(SurfaceHolder holder) {
- Log.v("SDL", "surfaceDestroyed()");
- // Call this *before* setting mIsSurfaceReady to 'false'
- SDLActivity.handlePause();
- SDLActivity.mIsSurfaceReady = false;
- SDLActivity.onNativeSurfaceDestroyed();
- }
-
- // Called when the surface is resized
- @Override
- public void surfaceChanged(SurfaceHolder holder,
- int format, int width, int height) {
- Log.v("SDL", "surfaceChanged()");
-
- int sdlFormat = 0x15151002; // SDL_PIXELFORMAT_RGB565 by default
- switch (format) {
- case PixelFormat.A_8:
- Log.v("SDL", "pixel format A_8");
- break;
- case PixelFormat.LA_88:
- Log.v("SDL", "pixel format LA_88");
- break;
- case PixelFormat.L_8:
- Log.v("SDL", "pixel format L_8");
- break;
- case PixelFormat.RGBA_4444:
- Log.v("SDL", "pixel format RGBA_4444");
- sdlFormat = 0x15421002; // SDL_PIXELFORMAT_RGBA4444
- break;
- case PixelFormat.RGBA_5551:
- Log.v("SDL", "pixel format RGBA_5551");
- sdlFormat = 0x15441002; // SDL_PIXELFORMAT_RGBA5551
- break;
- case PixelFormat.RGBA_8888:
- Log.v("SDL", "pixel format RGBA_8888");
- sdlFormat = 0x16462004; // SDL_PIXELFORMAT_RGBA8888
- break;
- case PixelFormat.RGBX_8888:
- Log.v("SDL", "pixel format RGBX_8888");
- sdlFormat = 0x16261804; // SDL_PIXELFORMAT_RGBX8888
- break;
- case PixelFormat.RGB_332:
- Log.v("SDL", "pixel format RGB_332");
- sdlFormat = 0x14110801; // SDL_PIXELFORMAT_RGB332
- break;
- case PixelFormat.RGB_565:
- Log.v("SDL", "pixel format RGB_565");
- sdlFormat = 0x15151002; // SDL_PIXELFORMAT_RGB565
- break;
- case PixelFormat.RGB_888:
- Log.v("SDL", "pixel format RGB_888");
- // Not sure this is right, maybe SDL_PIXELFORMAT_RGB24 instead?
- sdlFormat = 0x16161804; // SDL_PIXELFORMAT_RGB888
- break;
- default:
- Log.v("SDL", "pixel format unknown " + format);
- break;
- }
-
- mWidth = width;
- mHeight = height;
- SDLActivity.onNativeResize(width, height, sdlFormat);
- Log.v("SDL", "Window size:" + width + "x"+height);
-
- // Set mIsSurfaceReady to 'true' *before* making a call to handleResume
- SDLActivity.mIsSurfaceReady = true;
- SDLActivity.onNativeSurfaceChanged();
-
-
- if (SDLActivity.mSDLThread == null) {
- // This is the entry point to the C app.
- // Start up the C app thread and enable sensor input for the first time
-
- SDLActivity.mSDLThread = new Thread(new SDLMain(), "SDLThread");
- enableSensor(Sensor.TYPE_ACCELEROMETER, true);
- SDLActivity.mSDLThread.start();
-
- // Set up a listener thread to catch when the native thread ends
- new Thread(new Runnable(){
- @Override
- public void run(){
- try {
- SDLActivity.mSDLThread.join();
- }
- catch(Exception e){}
- finally{
- // Native thread has finished
- if (! SDLActivity.mExitCalledFromJava) {
- SDLActivity.handleNativeExit();
- }
- }
- }
- }).start();
- }
- }
-
- // unused
- @Override
- public void onDraw(Canvas canvas) {}
-
-
- // Xperia keys
- public int joy_to_keyboard_key(int key) {
- switch (key) {
- default:
- return key;
- }
-
- }
-
- // Key events
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- //Log.v("SDL", "key down: " + keyCode);
- SDLActivity.onNativeKeyDown(keyCode);
- return true;
- }
- else if (event.getAction() == KeyEvent.ACTION_UP) {
- //Log.v("SDL", "key up: " + keyCode);
- SDLActivity.onNativeKeyUp(keyCode);
- return true;
- }
-
- return false;
- }
-
- // Touch events
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- /* Ref: http://developer.android.com/training/gestures/multi.html */
- final int touchDevId = event.getDeviceId();
- final int pointerCount = event.getPointerCount();
- int action = event.getActionMasked();
- int pointerFingerId;
- int i = -1;
- float x,y,p;
-
- switch(action) {
- case MotionEvent.ACTION_MOVE:
- for (i = 0; i < pointerCount; i++) {
- pointerFingerId = event.getPointerId(i);
- x = event.getX(i) / mWidth;
- y = event.getY(i) / mHeight;
- p = event.getPressure(i);
- SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
- }
- break;
-
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_DOWN:
- // Primary pointer up/down, the index is always zero
- i = 0;
- case MotionEvent.ACTION_POINTER_UP:
- case MotionEvent.ACTION_POINTER_DOWN:
- // Non primary pointer up/down
- if (i == -1) {
- i = event.getActionIndex();
- }
-
- pointerFingerId = event.getPointerId(i);
- x = event.getX(i) / mWidth;
- y = event.getY(i) / mHeight;
- p = event.getPressure(i);
- SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
- break;
-
- default:
- break;
- }
-
- return true;
- }
-
- // Sensor events
- public void enableSensor(int sensortype, boolean enabled) {
- // TODO: This uses getDefaultSensor - what if we have >1 accels?
- if (enabled) {
- mSensorManager.registerListener(this,
- mSensorManager.getDefaultSensor(sensortype),
- SensorManager.SENSOR_DELAY_GAME, null);
- } else {
- mSensorManager.unregisterListener(this,
- mSensorManager.getDefaultSensor(sensortype));
- }
- }
-
- @Override
- public void onAccuracyChanged(Sensor sensor, int accuracy) {
- // TODO
- }
-
- @Override
- public void onSensorChanged(SensorEvent event) {
- if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
- float x, y;
- switch (mDisplay.getRotation()) {
- case Surface.ROTATION_90:
- x = -event.values[1];
- y = event.values[0];
- break;
- case Surface.ROTATION_270:
- x = event.values[1];
- y = -event.values[0];
- break;
- case Surface.ROTATION_180:
- x = -event.values[1];
- y = -event.values[0];
- break;
- default:
- x = event.values[0];
- y = event.values[1];
- break;
- }
- SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH,
- y / SensorManager.GRAVITY_EARTH,
- event.values[2] / SensorManager.GRAVITY_EARTH - 1);
- }
- }
-}
-
-/* This is a fake invisible editor view that receives the input and defines the
- * pan&scan region
- */
-class DummyEdit extends View implements View.OnKeyListener {
- InputConnection ic;
-
- public DummyEdit(Context context) {
- super(context);
- setFocusableInTouchMode(true);
- setFocusable(true);
- setOnKeyListener(this);
- }
-
- @Override
- public boolean onCheckIsTextEditor() {
- return true;
- }
-
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
-
- // This handles the hardware keyboard input
- if (event.isPrintingKey() || keyCode == 62) {
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1);
- }
- return true;
- }
-
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- SDLActivity.onNativeKeyDown(keyCode);
- return true;
- } else if (event.getAction() == KeyEvent.ACTION_UP) {
- SDLActivity.onNativeKeyUp(keyCode);
- return true;
- }
-
- return false;
- }
-
- //
- @Override
- public boolean onKeyPreIme (int keyCode, KeyEvent event) {
- // As seen on StackOverflow: http://stackoverflow.com/questions/7634346/keyboard-hide-event
- // FIXME: Discussion at http://bugzilla.libsdl.org/show_bug.cgi?id=1639
- // FIXME: This is not a 100% effective solution to the problem of detecting if the keyboard is showing or not
- // FIXME: A more effective solution would be to change our Layout from AbsoluteLayout to Relative or Linear
- // FIXME: And determine the keyboard presence doing this: http://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android
- // FIXME: An even more effective way would be if Android provided this out of the box, but where would the fun be in that :)
- if (event.getAction()==KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
- if (SDLActivity.mTextEdit != null && SDLActivity.mTextEdit.getVisibility() == View.VISIBLE) {
- SDLActivity.onNativeKeyboardFocusLost();
- }
- }
- return super.onKeyPreIme(keyCode, event);
- }
-
- @Override
- public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
- ic = new SDLInputConnection(this, true);
-
- outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI
- | 33554432 /* API 11: EditorInfo.IME_FLAG_NO_FULLSCREEN */;
-
- return ic;
- }
-}
-
-class SDLInputConnection extends BaseInputConnection {
-
- public SDLInputConnection(View targetView, boolean fullEditor) {
- super(targetView, fullEditor);
-
- }
-
- @Override
- public boolean sendKeyEvent(KeyEvent event) {
-
- /*
- * This handles the keycodes from soft keyboard (and IME-translated
- * input from hardkeyboard)
- */
- int keyCode = event.getKeyCode();
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- if (event.isPrintingKey() || keyCode == 62) {
- commitText(String.valueOf((char) event.getUnicodeChar()), 1);
- }
- SDLActivity.onNativeKeyDown(keyCode);
- return true;
- } else if (event.getAction() == KeyEvent.ACTION_UP) {
-
- SDLActivity.onNativeKeyUp(keyCode);
- return true;
- }
- return super.sendKeyEvent(event);
- }
-
- @Override
- public boolean commitText(CharSequence text, int newCursorPosition) {
-
- nativeCommitText(text.toString(), newCursorPosition);
-
- return super.commitText(text, newCursorPosition);
- }
-
- @Override
- public boolean setComposingText(CharSequence text, int newCursorPosition) {
-
- nativeSetComposingText(text.toString(), newCursorPosition);
-
- return super.setComposingText(text, newCursorPosition);
- }
-
- public native void nativeCommitText(String text, int newCursorPosition);
-
- public native void nativeSetComposingText(String text, int newCursorPosition);
-
- @Override
- public boolean deleteSurroundingText(int beforeLength, int afterLength) {
- // Workaround to capture backspace key. Ref: http://stackoverflow.com/questions/14560344/android-backspace-in-webview-baseinputconnection
- if (beforeLength == 1 && afterLength == 0) {
- // backspace
- return super.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))
- && super.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));
- }
-
- return super.deleteSurroundingText(beforeLength, afterLength);
- }
-}
-
-/* A null joystick handler for API level < 12 devices (the accelerometer is handled separately) */
-class SDLJoystickHandler {
-
- public boolean handleMotionEvent(MotionEvent event) {
- return false;
- }
-
- public void pollInputDevices() {
- }
-}
-
-/* Actual joystick functionality available for API >= 12 devices */
-class SDLJoystickHandler_API12 extends SDLJoystickHandler {
-
- class SDLJoystick {
- public int device_id;
- public String name;
- public ArrayList axes;
- public ArrayList hats;
- }
- class RangeComparator implements Comparator
- {
- @Override
- public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) {
- return arg0.getAxis() - arg1.getAxis();
- }
- }
-
- private ArrayList mJoysticks;
-
- public SDLJoystickHandler_API12() {
-
- mJoysticks = new ArrayList();
- }
-
- @Override
- public void pollInputDevices() {
- int[] deviceIds = InputDevice.getDeviceIds();
- // It helps processing the device ids in reverse order
- // For example, in the case of the XBox 360 wireless dongle,
- // so the first controller seen by SDL matches what the receiver
- // considers to be the first controller
-
- for(int i=deviceIds.length-1; i>-1; i--) {
- SDLJoystick joystick = getJoystick(deviceIds[i]);
- if (joystick == null) {
- joystick = new SDLJoystick();
- InputDevice joystickDevice = InputDevice.getDevice(deviceIds[i]);
- if( (joystickDevice.getSources() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
- joystick.device_id = deviceIds[i];
- joystick.name = joystickDevice.getName();
- joystick.axes = new ArrayList();
- joystick.hats = new ArrayList();
-
- List ranges = joystickDevice.getMotionRanges();
- Collections.sort(ranges, new RangeComparator());
- for (InputDevice.MotionRange range : ranges ) {
- if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 ) {
- if (range.getAxis() == MotionEvent.AXIS_HAT_X ||
- range.getAxis() == MotionEvent.AXIS_HAT_Y) {
- joystick.hats.add(range);
- }
- else {
- joystick.axes.add(range);
- }
- }
- }
-
- mJoysticks.add(joystick);
- SDLActivity.nativeAddJoystick(joystick.device_id, joystick.name, 0, -1,
- joystick.axes.size(), joystick.hats.size()/2, 0);
- }
- }
- }
-
- /* Check removed devices */
- ArrayList removedDevices = new ArrayList();
- for(int i=0; i < mJoysticks.size(); i++) {
- int device_id = mJoysticks.get(i).device_id;
- int j;
- for (j=0; j < deviceIds.length; j++) {
- if (device_id == deviceIds[j]) break;
- }
- if (j == deviceIds.length) {
- removedDevices.add(device_id);
- }
- }
-
- for(int i=0; i < removedDevices.size(); i++) {
- int device_id = removedDevices.get(i);
- SDLActivity.nativeRemoveJoystick(device_id);
- for (int j=0; j < mJoysticks.size(); j++) {
- if (mJoysticks.get(j).device_id == device_id) {
- mJoysticks.remove(j);
- break;
- }
- }
- }
- }
-
- protected SDLJoystick getJoystick(int device_id) {
- for(int i=0; i < mJoysticks.size(); i++) {
- if (mJoysticks.get(i).device_id == device_id) {
- return mJoysticks.get(i);
- }
- }
- return null;
- }
-
- @Override
- public boolean handleMotionEvent(MotionEvent event) {
- if ( (event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0) {
- int actionPointerIndex = event.getActionIndex();
- int action = event.getActionMasked();
- switch(action) {
- case MotionEvent.ACTION_MOVE:
- SDLJoystick joystick = getJoystick(event.getDeviceId());
- if ( joystick != null ) {
- for (int i = 0; i < joystick.axes.size(); i++) {
- InputDevice.MotionRange range = joystick.axes.get(i);
- /* Normalize the value to -1...1 */
- float value = ( event.getAxisValue( range.getAxis(), actionPointerIndex) - range.getMin() ) / range.getRange() * 2.0f - 1.0f;
- SDLActivity.onNativeJoy(joystick.device_id, i, value );
- }
- for (int i = 0; i < joystick.hats.size(); i+=2) {
- int hatX = Math.round(event.getAxisValue( joystick.hats.get(i).getAxis(), actionPointerIndex ) );
- int hatY = Math.round(event.getAxisValue( joystick.hats.get(i+1).getAxis(), actionPointerIndex ) );
- SDLActivity.onNativeHat(joystick.device_id, i/2, hatX, hatY );
- }
- }
- break;
- default:
- break;
- }
- }
- return true;
- }
-}
-
-class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener {
- // Generic Motion (mouse hover, joystick...) events go here
- // We only have joysticks yet
- @Override
- public boolean onGenericMotion(View v, MotionEvent event) {
- return SDLActivity.handleJoystickMotionEvent(event);
- }
-}
+package org.libsdl.app;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioTrack;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+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 com.beloko.games.hl.NativeLib;
+import com.beloko.touchcontrols.ControlInterpreter;
+import com.beloko.touchcontrols.Settings;
+import com.beloko.touchcontrols.TouchControlsSettings;
+
+/**
+ 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;
+
+ //Touch control interp
+ public static ControlInterpreter controlInterp;
+
+ /**
+ * 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",
+ "touchcontrols",
+ "xash"
+ };
+ }
+
+ // 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("SDL", "Device: " + android.os.Build.DEVICE);
+ Log.v("SDL", "Model: " + android.os.Build.MODEL);
+ Log.v("SDL", "onCreate():" + mSingleton);
+ super.onCreate(savedInstanceState);
+
+ // fullscreen
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
+ WindowManager.LayoutParams.FLAG_FULLSCREEN);
+
+ // keep screen on
+ getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
+ WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+
+ 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);
+ }
+
+ // Events
+ @Override
+ protected void onPause() {
+ Log.v("SDL", "onPause()");
+ super.onPause();
+
+ if (SDLActivity.mBrokenLibraries) {
+ return;
+ }
+
+ SDLActivity.handlePause();
+ }
+
+ @Override
+ protected void onResume() {
+ Log.v("SDL", "onResume()");
+ super.onResume();
+
+ if (SDLActivity.mBrokenLibraries) {
+ return;
+ }
+
+ SDLActivity.handleResume();
+ }
+
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+ Log.v("SDL", "onWindowFocusChanged(): " + hasFocus);
+
+ if (SDLActivity.mBrokenLibraries) {
+ return;
+ }
+
+ SDLActivity.mHasFocus = hasFocus;
+ if (hasFocus) {
+ SDLActivity.handleResume();
+ }
+ }
+
+ @Override
+ public void onLowMemory() {
+ Log.v("SDL", "onLowMemory()");
+ super.onLowMemory();
+
+ if (SDLActivity.mBrokenLibraries) {
+ return;
+ }
+
+ SDLActivity.nativeLowMemory();
+ }
+
+ @Override
+ protected void onDestroy() {
+ Log.v("SDL", "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("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) {
+
+ 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.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, 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 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