diff --git a/.gitignore b/.gitignore
index 4f08c5f41..972670fd8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -51,3 +51,20 @@ packets_log.txt
history.dat
README.dependencies
xx
+
+android/android-ndk*
+android/android-sdk*
+android/assets
+android/bin
+android/obj
+android/libs
+android-*
+*.apk
+
+lib/curl
+lib/freetype
+lib/ifaddrs
+lib/libogg
+lib/libvorbis
+lib/openal
+lib/openssl
diff --git a/android/Android.mk b/android/Android.mk
new file mode 100644
index 000000000..08728da5f
--- /dev/null
+++ b/android/Android.mk
@@ -0,0 +1,174 @@
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+
+# OpenAL
+LOCAL_MODULE := openal
+LOCAL_SRC_FILES := obj/openal/libopenal.a
+include $(PREBUILT_STATIC_LIBRARY)
+include $(CLEAR_VARS)
+
+
+# OGG
+LOCAL_MODULE := ogg
+LOCAL_SRC_FILES := obj/libogg/src/.libs/libogg.a
+include $(PREBUILT_STATIC_LIBRARY)
+include $(CLEAR_VARS)
+
+
+# Vorbis
+LOCAL_MODULE := vorbis
+LOCAL_SRC_FILES := obj/libvorbis/lib/.libs/libvorbis.a
+include $(PREBUILT_STATIC_LIBRARY)
+include $(CLEAR_VARS)
+
+
+# Vorbisfile
+LOCAL_MODULE := vorbisfile
+LOCAL_SRC_FILES := obj/libvorbis/lib/.libs/libvorbisfile.a
+include $(PREBUILT_STATIC_LIBRARY)
+include $(CLEAR_VARS)
+
+
+# CURL
+LOCAL_MODULE := curl
+LOCAL_SRC_FILES := obj/curl/lib/.libs/libcurl.a
+include $(PREBUILT_STATIC_LIBRARY)
+include $(CLEAR_VARS)
+
+
+# libcrypto
+LOCAL_MODULE := libcrypto
+LOCAL_SRC_FILES := obj/openssl/libcrypto.a
+include $(PREBUILT_STATIC_LIBRARY)
+include $(CLEAR_VARS)
+
+
+# libssl
+LOCAL_MODULE := libssl
+LOCAL_SRC_FILES := obj/openssl/libssl.a
+include $(PREBUILT_STATIC_LIBRARY)
+include $(CLEAR_VARS)
+
+
+# JPEG
+LOCAL_MODULE := jpeglib
+LOCAL_SRC_FILES := obj/jpeglib/libjpeglib.a
+include $(PREBUILT_STATIC_LIBRARY)
+include $(CLEAR_VARS)
+
+
+# Freetype
+LOCAL_MODULE := freetype
+LOCAL_SRC_FILES := obj/freetype/objs/.libs/libfreetype.a
+include $(PREBUILT_STATIC_LIBRARY)
+include $(CLEAR_VARS)
+
+
+# zlib
+LOCAL_MODULE := zlib
+LOCAL_SRC_FILES := obj/zlib/libz.a
+include $(PREBUILT_STATIC_LIBRARY)
+include $(CLEAR_VARS)
+
+
+# PNG
+LOCAL_MODULE := png
+LOCAL_SRC_FILES := obj/libpng/libpng.a
+include $(PREBUILT_STATIC_LIBRARY)
+include $(CLEAR_VARS)
+
+
+# ifaddrs
+LOCAL_MODULE := ifaddrs
+LOCAL_PATH := .
+LOCAL_SRC_FILES := ../lib/ifaddrs/ifaddrs.c
+LOCAL_CFLAGS := -I../lib/ifaddrs
+include $(BUILD_STATIC_LIBRARY)
+include $(CLEAR_VARS)
+
+
+# AngelScript
+LOCAL_MODULE := angelscript
+LOCAL_PATH := .
+LOCAL_CPP_FEATURES += rtti exceptions
+LOCAL_SRC_FILES := $(wildcard ../lib/angelscript/source/*.S) \
+ $(wildcard ../lib/angelscript/source/*.cpp)
+LOCAL_CFLAGS := -I../lib/angelscript/source/
+include $(BUILD_STATIC_LIBRARY)
+include $(CLEAR_VARS)
+
+
+# ENET
+LOCAL_MODULE := enet
+LOCAL_PATH := .
+LOCAL_CPP_FEATURES += rtti
+LOCAL_SRC_FILES := $(wildcard ../lib/enet/*.c)
+LOCAL_CFLAGS := -I../lib/enet/include/ -DHAS_SOCKLEN_T
+include $(BUILD_STATIC_LIBRARY)
+include $(CLEAR_VARS)
+
+
+# Bullet
+LOCAL_MODULE := bullet
+LOCAL_PATH := .
+LOCAL_CPP_FEATURES += rtti
+LOCAL_SRC_FILES := $(wildcard ../lib/bullet/src/*/*.cpp) \
+ $(wildcard ../lib/bullet/src/*/*/*.cpp)
+LOCAL_CFLAGS := -I../lib/bullet/src/
+include $(BUILD_STATIC_LIBRARY)
+include $(CLEAR_VARS)
+
+
+# Irrlicht
+LOCAL_MODULE := irrlicht
+LOCAL_PATH := .
+LOCAL_CPP_FEATURES += rtti
+LOCAL_SRC_FILES := $(wildcard ../lib/irrlicht/source/Irrlicht/*.cpp) \
+ $(wildcard ../lib/irrlicht/source/Irrlicht/Android/*.cpp)
+LOCAL_CFLAGS := -I../lib/irrlicht/source/Irrlicht/ \
+ -I../lib/irrlicht/include/ \
+ -Iobj/jpeglib/ \
+ -Iobj/libpng/ \
+ -Iobj/zlib/ \
+ -I$(call my-dir)/../../sources/android/native_app_glue
+ -std=gnu++0x
+LOCAL_STATIC_LIBRARIES := jpeglib png zlib
+include $(BUILD_STATIC_LIBRARY)
+include $(CLEAR_VARS)
+
+
+# STK
+LOCAL_MODULE := main
+LOCAL_PATH := .
+LOCAL_CPP_FEATURES += rtti exceptions
+LOCAL_SRC_FILES := $(wildcard ../src/*.cpp) \
+ $(wildcard ../src/*/*.cpp) \
+ $(wildcard ../src/*/*/*.cpp)
+LOCAL_LDLIBS := -llog -landroid -lEGL -lGLESv3 -lOpenSLES
+LOCAL_CFLAGS := -I../lib/angelscript/include \
+ -I../lib/bullet/src \
+ -I../lib/enet/include \
+ -I../lib/ifaddrs \
+ -I../lib/irrlicht/include \
+ -I../lib/irrlicht/source/Irrlicht \
+ -I../src \
+ -Iobj/curl/include \
+ -Iobj/freetype/include \
+ -Iobj/libogg/include \
+ -Iobj/libvorbis/include \
+ -Iobj/openal/include \
+ -I$(call my-dir)/../../sources/android/native_app_glue \
+ -DUSE_GLES2 \
+ -DHAVE_OGGVORBIS \
+ -DNDEBUG \
+ -std=gnu++0x
+
+LOCAL_STATIC_LIBRARIES := irrlicht bullet enet freetype ifaddrs angelscript \
+ vorbisfile vorbis ogg openal curl libssl libcrypto \
+ gnustl_static android_native_app_glue
+
+include $(BUILD_SHARED_LIBRARY)
+include $(CLEAR_VARS)
+
+$(call import-module,android/native_app_glue)
diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml
new file mode 100644
index 000000000..1a6a1f61d
--- /dev/null
+++ b/android/AndroidManifest.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/README.ANDROID b/android/README.ANDROID
new file mode 100644
index 000000000..85903d41d
--- /dev/null
+++ b/android/README.ANDROID
@@ -0,0 +1,134 @@
+================================================================================
+
+ SUPERTUXKART
+
+================================================================================
+
+
+
+--------------------------------------------------------------------------------
+ SYSTEM REQUIREMENTS
+--------------------------------------------------------------------------------
+
+To run SuperTuxKart on Android, you need a device that meets following
+requirements:
+
+- Android 4.4 or later
+- Processor compatible with armv7 or x86
+- GPU that supports OpenGL ES 3.0
+- 1 GB RAM (STK uses ~150 MB in minimal configuration)
+- 300 MB of free space on internal storage
+- Touch screen or external keyboard
+
+
+
+--------------------------------------------------------------------------------
+ COMPILATION
+--------------------------------------------------------------------------------
+
+The build scripts are designed to run under linux. They may work under cygwin
+after some tweaks, but atm. only linux is supported.
+
+Dependencies list (may be incomplete):
+
+ autoconf, automake, make, python, ant, imagemagick, cmake, vorbis-tools
+
+Additionally some dependencies for optimize_data script:
+
+ advancecomp, libjpeg-progs, optipng
+
+Before compilation you must download the package with dependencies from:
+https://github.com/supertuxkart/dependencies
+and extract it to stk-code/lib. It contains sources of libraries that are used
+in STK, but are not availiable in stk-code repository (curl, freetype, openal).
+
+You need also Android SDK for android-19 platform (the API for Android 4.4) and
+Android NDK (versions r12b and r13b have been tested).
+
+You need to create proper "android-sdk" and "android-ndk" symlinks in the
+directory with Android project, so that the compilation script will have access
+to the SDK and NDK.
+
+These paths can be also set in SDK_PATH and NDK_PATH environmental variables.
+
+Before running the compilation, run the generate_assets script, so that
+selected assets will be copied to "assets" directory, and then included in the
+apk file.
+
+You can select different karts and tracks by setting KARTS and TRACKS variables
+in the generate_assets.sh script at the beginning of file.
+
+When you are creating the assets directory manually, note that the
+directories.txt file is urgently needed and it is used by the application for
+extracting assets.
+
+If the assets directory is already prepared, you can run "./make.sh" command to
+build the project and create an apk file. Note that all arguments are passed to
+the make command, so that you can run "./make.sh -j5" for multi-threaded build.
+
+If you want to prepare a package for particular architecture, you can choose it
+by setting the COMPILE_ARCH environmental variable. At this stage, supported
+architectures are "armv7", "x86" and "aarch64". The default is "armv7".
+
+Basically if all dependencies are installed in the system, it should be enough
+to just run:
+
+ export SDK_PATH=/path/to/your/android/sdk
+ export NDK_PATH=/path/to/your/android/ndk
+ ./generate_assets.sh
+ ./make.sh -j5
+
+
+
+--------------------------------------------------------------------------------
+ KNOWN ISSUES
+--------------------------------------------------------------------------------
+
+1. At this stage only shader-based (OpenGL ES 3.0) pipeline works. The fixed
+ pipeline (GLES 2.0) could work (it works under linux), but it doesn't look
+ good and is generally broken. It means that it's not possible to run STK on
+ Android 4.2 or older. It is technically possible to do - check GLES context
+ version, load OpenGL functions dynamically using EGL, and if they are not
+ loaded properly, then fallback to GLES 2.0. But these devices may be too
+ slow to run STK anyway.
+
+2. It never ocurred for me, but it's possible that EGL context is lost in some
+ cases. SuperTuxKart is not designed to re-create all textures at any moment,
+ so this is a "Wontfix", at least for now.
+
+3. Some bright tracks (Farm, Gran Paradiso) seem to be a bit darker in GLES
+ renderer than in original OpenGL 3.x renderer. It can be easily hacked by
+ adding few lines to object pass shader, but we should rather try to find the
+ real reason.
+
+4. Explosion effect has poor performance and causes fps drop for a while.
+ Though it can be easily tweaked, so that less particles per second is
+ generated.
+
+5. Touch steering needs nice button icons.
+
+6. We use "exit(0)" at the end of main function. We shouldn't do it and we
+ should just return from the main function. But STK uses some global
+ variables and their values are remembered when the game is restarted. We
+ should properly clear them or re-initialize on startup. Using the "exit(0)"
+ is not-that-bad workaround, but it may cause a crash on exit sometimes.
+ It seems to affect only Android 5.0. More information about the crash:
+ https://code.google.com/p/android/issues/detail?id=160824
+
+7. STK crashes on Qualcomm with Adreno 305 when trying to draw menu interface.
+ Backtrace shows glDrawElements function, and internally crashed in vbo
+ allocation.
+
+8. STK crashes on startup on some devices when aarch64 build is made using
+ Android r13 NDK. The r13 version has rather big modifications (it uses clang
+ instead of gcc by default). This is probably a bug in NDK/compiler/OS, but
+ for this reason using NDK r12 for 64-bit arm compilation is preferred.
+
+9. Angelscript doesn't have full support for aarch64 builds, so that scripting
+ won't work on this platform.
+
+10. Steering with accelerometer is not available yet. It needs some work to do
+ it properly because tablets have different screen orientation than phones,
+ so that they receive events from different axies during rotating the device.
+ As far as I see it's not possible to read default screen orientation using
+ NDK functions.
diff --git a/android/build.xml b/android/build.xml
new file mode 100644
index 000000000..54c3d58b4
--- /dev/null
+++ b/android/build.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/android/generate_assets.sh b/android/generate_assets.sh
new file mode 100755
index 000000000..2fdec9214
--- /dev/null
+++ b/android/generate_assets.sh
@@ -0,0 +1,240 @@
+#!/bin/sh
+#
+# (C) 2016-2017 Dawid Gan, under the GPLv3
+#
+# A script that generates data files for Android apk
+
+
+# Below are simple configuration variables
+# It's allowed to set "all" for KARTS and TRACKS if it's intended to create
+# package with full data.
+# The karts and tracks directories shouldn't exist in ASSETS_DIRS variable
+# because they are handled separately
+# The TEXTURE_SIZE and SOUND_QUALITY take effect only if DECREASE_QUALITY has
+# value greater than 0
+# The script needs imagemagick and ogg utils installed to use DECREASE_QUALITY
+# feature
+
+################################################################################
+
+export KARTS="tux nolok xue"
+export TRACKS="battleisland cornfield_crossing featunlocked gplose gpwin \
+ hacienda introcutscene introcutscene2 lighthouse olivermath \
+ overworld snowmountain snowtuxpeak soccer_field tutorial"
+
+export ASSETS_PATHS="../data \
+ ../../stk-assets \
+ ../../supertuxkart-assets"
+
+export ASSETS_DIRS="library models music sfx textures"
+
+export TEXTURE_SIZE=256
+export SOUND_QUALITY=64
+
+export RUN_OPTIMIZE_SCRIPT=0
+export DECREASE_QUALITY=1
+
+################################################################################
+
+
+cd "`dirname "$0"`"
+
+# Find assets path
+for ASSETS_PATH in $ASSETS_PATHS; do
+ if [ -d $ASSETS_PATH ] && [ `ls $ASSETS_PATH | grep -c tracks` -gt 0 ]; then
+ echo "Assets found in $ASSETS_PATH"
+ ASSETS_PATH_FOUND=1
+ break
+ fi
+done
+
+if [ -z $ASSETS_PATH_FOUND ]; then
+ echo "Couldn't find assets path"
+ exit 1
+fi
+
+if [ ! -d "../data" ]; then
+ echo "Couldn't find data directory"
+ exit 1
+fi
+
+
+# Clear previous assets directory
+echo "Clear previous assets directory"
+rm -rf assets
+
+
+# Copy all assets
+echo "Copy all assets"
+
+mkdir -p assets/data
+
+for DIR in `ls $ASSETS_PATH`; do
+ CAN_BE_COPIED=0
+
+ for ASSETS_DIR in $ASSETS_DIRS; do
+ if [ $DIR = $ASSETS_DIR ]; then
+ CAN_BE_COPIED=1
+ break
+ fi
+ done;
+
+ # Don't copy karts and tracks. It will be handled later
+ BLACKLIST_ASSETS="karts tracks"
+ for ASSETS_DIR in $BLACKLIST_ASSETS; do
+ if [ $DIR = $ASSETS_DIR ]; then
+ CAN_BE_COPIED=0
+ break
+ fi
+ done;
+
+ if [ $CAN_BE_COPIED -gt 0 ]; then
+ cp -a "$ASSETS_PATH/$DIR" assets/data/
+ fi
+done;
+
+
+# Copy selected tracks
+echo "Copy selected tracks"
+
+mkdir -p assets/data/tracks
+
+for DIR in `ls $ASSETS_PATH/tracks`; do
+ CAN_BE_COPIED=0
+
+ if [ "$TRACKS" != "all" ]; then
+ for TRACK in $TRACKS; do
+ if [ $DIR = $TRACK ]; then
+ CAN_BE_COPIED=1
+ break
+ fi
+ done;
+ else
+ CAN_BE_COPIED=1
+ fi
+
+ if [ $CAN_BE_COPIED -gt 0 ]; then
+ cp -a "$ASSETS_PATH/tracks/$DIR" assets/data/tracks/
+ fi
+done
+
+
+# Copy selected karts
+echo "Copy selected karts"
+
+mkdir -p assets/data/karts
+
+for DIR in `ls $ASSETS_PATH/karts`; do
+ CAN_BE_COPIED=0
+
+ if [ "$KARTS" != "all" ]; then
+ for KART in $KARTS; do
+ if [ $DIR = $KART ]; then
+ CAN_BE_COPIED=1
+ break
+ fi
+ done;
+ else
+ CAN_BE_COPIED=1
+ fi
+
+ if [ $CAN_BE_COPIED -gt 0 ]; then
+ cp -a "$ASSETS_PATH/karts/$DIR" assets/data/karts/
+ fi
+done
+
+
+# Decrease assets quality in order to save some disk space and RAM
+echo "Decrease assets quality"
+
+convert_image()
+{
+ if [ -z "$1" ]; then
+ echo "No file to convert"
+ return
+ fi
+
+ FILE="$1"
+
+ W=`identify -format "%[fx:w]" "$FILE"`
+ H=`identify -format "%[fx:h]" "$FILE"`
+
+ if [ -z $W ] || [ -z $H ]; then
+ echo "Couldn't convert $FILE file"
+ return
+ fi
+
+ if [ $W -le $TEXTURE_SIZE ] && [ $H -le $TEXTURE_SIZE ]; then
+ return
+ fi
+
+ if [ $W -gt $H ]; then
+ SCALED_W=$TEXTURE_SIZE
+ SCALED_H=$(($TEXTURE_SIZE * $H / $W))
+ else
+ SCALED_W=$(($TEXTURE_SIZE * $W / $H))
+ SCALED_H=$TEXTURE_SIZE
+ fi
+
+ convert -scale $SCALED_WE\x$SCALED_H "$FILE" "$FILE"
+}
+
+convert_sound()
+{
+ if [ -z "$1" ]; then
+ echo "No file to convert"
+ return
+ fi
+
+ FILE="$1"
+
+ oggdec "$FILE" -o tmp.wav
+
+ if [ -s tmp.wav ]; then
+ oggenc --downmix -b $SOUND_QUALITY tmp.wav -o tmp.ogg
+ fi
+
+ if [ -s tmp.ogg ]; then
+ SIZE_OLD=`du -k "$FILE" | cut -f1`
+ SIZE_NEW=`du -k "tmp.ogg" | cut -f1`
+
+ if [ $SIZE_NEW -lt $SIZE_OLD ]; then
+ mv tmp.ogg "$FILE"
+ fi
+ fi
+
+ rm -f tmp.wav tmp.ogg
+}
+
+if [ $DECREASE_QUALITY -gt 0 ]; then
+ find assets/data -iname "*.png" | while read f; do convert_image "$f"; done
+ find assets/data -iname "*.jpg" | while read f; do convert_image "$f"; done
+ find assets/data -iname "*.ogg" | while read f; do convert_sound "$f"; done
+fi
+
+
+# Copy data directory
+echo "Copy data directory"
+cp -a ../data/* assets/data/
+
+
+# Run optimize_data.sh script
+if [ $RUN_OPTIMIZE_SCRIPT -gt 0 ]; then
+ echo "Run optimize_data.sh script"
+ sh -c 'cd assets/data; ../../../data/optimize_data.sh'
+fi
+
+
+# Generate directories list
+echo "Generate directories list"
+find assets/* -type d > assets/directories.txt
+sed -i s/'.\/assets\/'// assets/directories.txt
+sed -i s/'assets\/'// assets/directories.txt
+
+
+# It will be probably ignored by ant, but create it anyway...
+touch assets/.nomedia
+
+
+echo "Done."
+exit 0
diff --git a/android/make.sh b/android/make.sh
new file mode 100755
index 000000000..ee7971e0e
--- /dev/null
+++ b/android/make.sh
@@ -0,0 +1,288 @@
+#!/bin/sh
+#
+# (C) 2016-2017 Dawid Gan, under the GPLv3
+#
+# A script that creates the apk build
+
+
+export DIRNAME=$(realpath "$(dirname "$0")")
+
+export NDK_PATH_DEFAULT="$DIRNAME/android-ndk"
+export SDK_PATH_DEFAULT="$DIRNAME/android-sdk"
+
+export NDK_TOOLCHAIN_PATH="$DIRNAME/obj/bin"
+export NDK_BUILD_SCRIPT="$DIRNAME/Android.mk"
+export PATH="$DIRNAME/obj/bin:$PATH"
+export CROSS_SYSROOT="$DIRNAME/obj/sysroot"
+
+#export NDK_CCACHE=ccache
+export NDK_CPPFLAGS="-O3 -g"
+
+export NDK_ABI_ARMV7=armeabi-v7a
+export ARCH_ARMV7=arm
+export HOST_ARMV7=arm-linux-androideabi
+export NDK_PLATFORM_ARMV7=android-19
+
+export NDK_ABI_X86=x86
+export ARCH_X86=x86
+export HOST_X86=i686-linux-android
+export NDK_PLATFORM_X86=android-19
+
+export NDK_ABI_AARCH64=arm64-v8a
+export ARCH_AARCH64=arm64
+export HOST_AARCH64=aarch64-linux-android
+export NDK_PLATFORM_AARCH64=android-21
+
+
+# A helper function that checks if error ocurred
+check_error()
+{
+ if [ $? -gt 0 ]; then
+ echo "Error ocurred."
+ exit
+ fi
+}
+
+# Handle clean command
+if [ ! -z "$1" ] && [ "$1" = "clean" ]; then
+ rm -rf bin
+ rm -rf libs
+ rm -rf obj
+ exit
+fi
+
+# Check if compilation for different platform has been started before
+if [ -f "$DIRNAME/obj/compile_arch" ]; then
+ PROJECT_ARCH=$(cat "$DIRNAME/obj/compile_arch")
+
+ if [ -z "$COMPILE_ARCH" ]; then
+ COMPILE_ARCH="$PROJECT_ARCH"
+ elif [ "$PROJECT_ARCH" != "$COMPILE_ARCH" ]; then
+ echo "Error: Compilation for different platform has been already made."
+ echo "Run './make.sh clean' first or set COMPILE_ARCH variable" \
+ "to '$PROJECT_ARCH.'"
+ exit
+ fi
+fi
+
+if [ -z "$COMPILE_ARCH" ]; then
+ COMPILE_ARCH="armv7"
+fi
+
+# Update variables for selected architecture
+if [ "$COMPILE_ARCH" = "armv7" ]; then
+ export NDK_PLATFORM=$NDK_PLATFORM_ARMV7
+ export NDK_ABI=$NDK_ABI_ARMV7
+ export ARCH=$ARCH_ARMV7
+ export HOST=$HOST_ARMV7
+elif [ "$COMPILE_ARCH" = "x86" ]; then
+ export NDK_PLATFORM=$NDK_PLATFORM_X86
+ export NDK_ABI=$NDK_ABI_X86
+ export ARCH=$ARCH_X86
+ export HOST=$HOST_X86
+elif [ "$COMPILE_ARCH" = "aarch64" ]; then
+ export NDK_PLATFORM=$NDK_PLATFORM_AARCH64
+ export NDK_ABI=$NDK_ABI_AARCH64
+ export ARCH=$ARCH_AARCH64
+ export HOST=$HOST_AARCH64
+else
+ echo "Unknow COMPILE_ARCH: $COMPILE_ARCH. Possible values are: " \
+ "armv7, aarch64, x86"
+ exit
+fi
+
+# Check if we have access to the Android NDK and SDK
+if [ -z "$NDK_PATH" ]; then
+ export NDK_PATH="$NDK_PATH_DEFAULT"
+fi
+
+if [ -z "$SDK_PATH" ]; then
+ export SDK_PATH="$SDK_PATH_DEFAULT"
+fi
+
+NDK_PATH=$(realpath "$NDK_PATH")
+SDK_PATH=$(realpath "$SDK_PATH")
+
+if [ ! -d "$NDK_PATH" ]; then
+ echo "Error: Couldn't find $NDK_PATH directory. Please create a symlink" \
+ "to your Android NDK installation in the $NDK_PATH_DEFAULT or set" \
+ "proper path in the NDK_PATH variable"
+ exit
+fi
+
+if [ ! -d "$SDK_PATH" ]; then
+ echo "Error: Couldn't find $SDK_PATH directory. Please create a symlink" \
+ "to your Android SDK installation in the $SDK_PATH_DEFAULT or set" \
+ "proper path in the SDK_PATH variable"
+ exit
+fi
+
+# Standalone toolchain
+if [ ! -f "$DIRNAME/obj/make_standalone_toolchain.stamp" ]; then
+ echo "Creating standalone toolchain"
+ rm -rf "$DIRNAME/obj"
+ ${NDK_PATH}/build/tools/make-standalone-toolchain.sh \
+ --platform=$NDK_PLATFORM \
+ --install-dir="$DIRNAME/obj/" \
+ --arch=$ARCH
+ check_error
+ touch "$DIRNAME/obj/make_standalone_toolchain.stamp"
+ echo $COMPILE_ARCH > "$DIRNAME/obj/compile_arch"
+fi
+
+# Freetype
+if [ ! -f "$DIRNAME/obj/freetype.stamp" ]; then
+ echo "Compiling freetype"
+ mkdir -p "$DIRNAME/obj/freetype"
+ cp -a -f "$DIRNAME/../lib/freetype/"* "$DIRNAME/obj/freetype"
+
+ cd "$DIRNAME/obj/freetype"
+ ./configure --host=$HOST \
+ --without-zlib \
+ --without-png \
+ --without-harfbuzz &&
+ make $@
+ check_error
+ touch "$DIRNAME/obj/freetype.stamp"
+fi
+
+# Zlib
+if [ ! -f "$DIRNAME/obj/zlib.stamp" ]; then
+ echo "Compiling zlib"
+ mkdir -p "$DIRNAME/obj/zlib"
+ cp -a -f "$DIRNAME/../lib/zlib/"* "$DIRNAME/obj/zlib"
+
+ cd "$DIRNAME/obj/zlib"
+ cmake . -DCMAKE_TOOLCHAIN_FILE=../../../cmake/Toolchain-android.cmake \
+ -DHOST=$HOST &&
+ make $@
+ check_error
+ touch "$DIRNAME/obj/zlib.stamp"
+fi
+
+# Libpng
+if [ ! -f "$DIRNAME/obj/libpng.stamp" ]; then
+ echo "Compiling libpng"
+ mkdir -p "$DIRNAME/obj/libpng"
+ mkdir -p "$DIRNAME/obj/libpng/lib"
+ cp -a -f "$DIRNAME/../lib/libpng/"* "$DIRNAME/obj/libpng"
+
+ cd "$DIRNAME/obj/libpng"
+ cmake . -DCMAKE_TOOLCHAIN_FILE=../../../cmake/Toolchain-android.cmake \
+ -DHOST=$HOST \
+ -DZLIB_LIBRARY="$DIRNAME/obj/zlib/libz.a" \
+ -DZLIB_INCLUDE_DIR="$DIRNAME/obj/zlib/" \
+ -DPNG_TESTS=0 &&
+ make $@
+ check_error
+ touch "$DIRNAME/obj/libpng.stamp"
+fi
+
+# Openal
+if [ ! -f "$DIRNAME/obj/openal.stamp" ]; then
+ echo "Compiling openal"
+ mkdir -p "$DIRNAME/obj/openal"
+ cp -a -f "$DIRNAME/../lib/openal/"* "$DIRNAME/obj/openal"
+
+ cd "$DIRNAME/obj/openal"
+ cmake . -DCMAKE_TOOLCHAIN_FILE=../../../cmake/Toolchain-android.cmake \
+ -DHOST=$HOST \
+ -DALSOFT_UTILS=0 \
+ -DALSOFT_EXAMPLES=0 \
+ -DALSOFT_TESTS=0 \
+ -DLIBTYPE=STATIC &&
+ make $@
+ check_error
+ touch "$DIRNAME/obj/openal.stamp"
+fi
+
+# OpenSSL
+if [ ! -f "$DIRNAME/obj/openssl.stamp" ]; then
+ echo "Compiling openssl"
+ mkdir -p "$DIRNAME/obj/openssl"
+ cp -a -f "$DIRNAME/../lib/openssl/"* "$DIRNAME/obj/openssl"
+
+ cd "$DIRNAME/obj/openssl"
+ ./Configure android --cross-compile-prefix="$HOST-"
+ make $@
+ check_error
+ touch "$DIRNAME/obj/openssl.stamp"
+fi
+
+# Curl
+if [ ! -f "$DIRNAME/obj/curl.stamp" ]; then
+ echo "Compiling curl"
+ mkdir -p "$DIRNAME/obj/curl"
+ cp -a -f "$DIRNAME/../lib/curl/"* "$DIRNAME/obj/curl"
+
+ cd "$DIRNAME/obj/curl"
+ CPPFLAGS="-I$DIRNAME/obj/openssl/include $CPPFLAGS" \
+ LDFLAGS="-L$DIRNAME/obj/openssl/ $LDFLAGS" \
+ ./configure --host=$HOST \
+ --with-ssl \
+ --disable-shared \
+ --enable-static \
+ --enable-threaded-resolver &&
+ make $@
+ check_error
+ touch "$DIRNAME/obj/curl.stamp"
+fi
+
+# Jpeglib
+if [ ! -f "$DIRNAME/obj/jpeglib.stamp" ]; then
+ echo "Compiling jpeglib"
+ mkdir -p "$DIRNAME/obj/jpeglib"
+ cp -a -f "$DIRNAME/../lib/jpeglib/"* "$DIRNAME/obj/jpeglib"
+
+ cd "$DIRNAME/obj/jpeglib"
+ cmake . -DCMAKE_TOOLCHAIN_FILE=../../../cmake/Toolchain-android.cmake \
+ -DHOST=$HOST &&
+ make $@
+ check_error
+ touch "$DIRNAME/obj/jpeglib.stamp"
+fi
+
+# Libogg
+if [ ! -f "$DIRNAME/obj/libogg.stamp" ]; then
+ echo "Compiling libogg"
+ mkdir -p "$DIRNAME/obj/libogg"
+ cp -a -f "$DIRNAME/../lib/libogg/"* "$DIRNAME/obj/libogg"
+
+ cd "$DIRNAME/obj/libogg"
+ ./configure --host=$HOST &&
+ make $@
+ check_error
+ touch "$DIRNAME/obj/libogg.stamp"
+fi
+
+# Libvorbis
+if [ ! -f "$DIRNAME/obj/libvorbis.stamp" ]; then
+ echo "Compiling libvorbis"
+ mkdir -p "$DIRNAME/obj/libvorbis"
+ cp -a -f "$DIRNAME/../lib/libvorbis/"* "$DIRNAME/obj/libvorbis"
+
+ cd "$DIRNAME/obj/libvorbis"
+ CPPFLAGS="-I$DIRNAME/obj/libogg/include $CPPFLAGS" \
+ LDFLAGS="-L$DIRNAME/obj/libogg/src/.libs $LDFLAGS" \
+ ./configure --host=$HOST &&
+ make $@
+ check_error
+ touch "$DIRNAME/obj/libvorbis.stamp"
+fi
+
+# STK
+echo "Compiling STK"
+cd "$DIRNAME"
+${NDK_PATH}/ndk-build $@ \
+ APP_BUILD_SCRIPT="$NDK_BUILD_SCRIPT" \
+ APP_ABI="$NDK_ABI" \
+ APP_PLATFORM="$NDK_PLATFORM" \
+ APP_CPPFLAGS="$NDK_CPPFLAGS" \
+ APP_STL=gnustl_static
+
+check_error
+
+# Build apk
+echo "Building APK"
+ant debug -Dsdk.dir="$SDK_PATH" -Dtarget=$NDK_PLATFORM
+check_error
diff --git a/android/res/drawable-hdpi/icon.png b/android/res/drawable-hdpi/icon.png
new file mode 100644
index 000000000..33de3e803
Binary files /dev/null and b/android/res/drawable-hdpi/icon.png differ
diff --git a/android/res/drawable-mdpi/icon.png b/android/res/drawable-mdpi/icon.png
new file mode 100644
index 000000000..0a8f484e6
Binary files /dev/null and b/android/res/drawable-mdpi/icon.png differ
diff --git a/android/res/drawable-xhdpi/icon.png b/android/res/drawable-xhdpi/icon.png
new file mode 100644
index 000000000..6f4383226
Binary files /dev/null and b/android/res/drawable-xhdpi/icon.png differ
diff --git a/android/res/drawable-xxhdpi/icon.png b/android/res/drawable-xxhdpi/icon.png
new file mode 100644
index 000000000..7bbbc461b
Binary files /dev/null and b/android/res/drawable-xxhdpi/icon.png differ
diff --git a/android/res/drawable/icon.png b/android/res/drawable/icon.png
new file mode 100644
index 000000000..a7b389fe7
Binary files /dev/null and b/android/res/drawable/icon.png differ
diff --git a/android/res/values/strings.xml b/android/res/values/strings.xml
new file mode 100644
index 000000000..931a3c2c9
--- /dev/null
+++ b/android/res/values/strings.xml
@@ -0,0 +1,4 @@
+
+
+ SuperTuxKart
+
diff --git a/cmake/Toolchain-android.cmake b/cmake/Toolchain-android.cmake
new file mode 100644
index 000000000..0ab179eee
--- /dev/null
+++ b/cmake/Toolchain-android.cmake
@@ -0,0 +1,32 @@
+# Cross-compiling requires CMake 2.6 or newer. Example:
+# cmake .. -DCMAKE_TOOLCHAIN_FILE=../XCompile-Android.txt -DHOST=arm-linux-androideabi
+# Where 'arm-linux-androideabi' is the host prefix for the cross-compiler. If
+# you already have a toolchain file setup, you may use that instead of this
+# file. Make sure to set CMAKE_FIND_ROOT_PATH to where the NDK toolchain was
+# installed (e.g. "$ENV{HOME}/toolchains/arm-linux-androideabi-r10c-21").
+
+# the name of the target operating system
+SET(CMAKE_SYSTEM_NAME Linux)
+
+# which compilers to use for C and C++
+SET(CMAKE_C_COMPILER "${HOST}-gcc")
+SET(CMAKE_CXX_COMPILER "${HOST}-g++")
+SET(CMAKE_RC_COMPILER "${HOST}-windres")
+
+# here is the target environment located
+SET(CMAKE_FIND_ROOT_PATH $ENV{NDK_TOOLCHAIN_PATH})
+
+# here is where stuff gets installed to
+SET(CMAKE_INSTALL_PREFIX "${CMAKE_FIND_ROOT_PATH}" CACHE STRING "Install path prefix, prepended onto install directories." FORCE)
+
+# adjust the default behaviour of the FIND_XXX() commands:
+# search headers and libraries in the target environment, search
+# programs in the host environment
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+
+# set env vars so that pkg-config will look in the appropriate directory for
+# .pc files (as there seems to be no way to force using ${HOST}-pkg-config)
+set(ENV{PKG_CONFIG_LIBDIR} "${CMAKE_INSTALL_PREFIX}/lib/pkgconfig")
+set(ENV{PKG_CONFIG_PATH} "")
diff --git a/lib/irrlicht/CMakeLists.txt b/lib/irrlicht/CMakeLists.txt
index 03d2ec125..ac3e0d49c 100644
--- a/lib/irrlicht/CMakeLists.txt
+++ b/lib/irrlicht/CMakeLists.txt
@@ -112,6 +112,7 @@ source/Irrlicht/CImageLoaderPNG.cpp
source/Irrlicht/CImageWriterBMP.cpp
source/Irrlicht/CImageWriterJPG.cpp
source/Irrlicht/CImageWriterPNG.cpp
+source/Irrlicht/CIrrDeviceAndroid.cpp
source/Irrlicht/CIrrDeviceConsole.cpp
source/Irrlicht/CIrrDeviceFB.cpp
source/Irrlicht/CIrrDeviceLinux.cpp
@@ -243,6 +244,7 @@ source/Irrlicht/CImageLoaderPNG.h
source/Irrlicht/CImageWriterBMP.h
source/Irrlicht/CImageWriterJPG.h
source/Irrlicht/CImageWriterPNG.h
+source/Irrlicht/CIrrDeviceAndroid.h
source/Irrlicht/CIrrDeviceConsole.h
source/Irrlicht/CIrrDeviceFB.h
source/Irrlicht/CIrrDeviceLinux.h
diff --git a/lib/irrlicht/source/Irrlicht/CIrrDeviceAndroid.cpp b/lib/irrlicht/source/Irrlicht/CIrrDeviceAndroid.cpp
new file mode 100644
index 000000000..3e491ff4b
--- /dev/null
+++ b/lib/irrlicht/source/Irrlicht/CIrrDeviceAndroid.cpp
@@ -0,0 +1,963 @@
+// Copyright (C) 2002-2007 Nikolaus Gebhardt
+// Copyright (C) 2007-2011 Christian Stehno
+// Copyright (C) 2016-2017 Dawid Gan
+// This file is part of the "Irrlicht Engine".
+// For conditions of distribution and use, see copyright notice in irrlicht.h
+
+#include "CIrrDeviceAndroid.h"
+
+#ifdef _IRR_COMPILE_WITH_ANDROID_DEVICE_
+
+#include
+#include "os.h"
+#include "CFileSystem.h"
+#include "COGLES2Driver.h"
+
+namespace irr
+{
+ namespace video
+ {
+ IVideoDriver* createOGLES1Driver(const SIrrlichtCreationParameters& params,
+ video::SExposedVideoData& data, io::IFileSystem* io);
+
+ IVideoDriver* createOGLES2Driver(const SIrrlichtCreationParameters& params,
+ video::SExposedVideoData& data, io::IFileSystem* io);
+ }
+}
+
+namespace irr
+{
+
+// These variables must be global. Otherwise initialization will reach infinite
+// loop after creating the device second time (i.e. the NULL driver and then
+// GLES2 driver). We get initialization events from Android only once.
+bool CIrrDeviceAndroid::IsPaused = true;
+bool CIrrDeviceAndroid::IsFocused = false;
+bool CIrrDeviceAndroid::IsStarted = false;
+bool CIrrDeviceAndroid::IsClosing = false;
+
+//! constructor
+CIrrDeviceAndroid::CIrrDeviceAndroid(const SIrrlichtCreationParameters& param)
+ : CIrrDeviceStub(param),
+ Accelerometer(0),
+ Gyroscope(0),
+ IsMousePressed(false)
+{
+ #ifdef _DEBUG
+ setDebugName("CIrrDeviceAndroid");
+ #endif
+
+ Android = (android_app *)(param.PrivateData);
+
+ IsClosing = Android->destroyRequested;
+
+ Android->userData = this;
+ Android->onAppCmd = handleAndroidCommand;
+ Android->onInputEvent = handleInput;
+
+ SensorManager = ASensorManager_getInstance();
+ SensorEventQueue = ASensorManager_createEventQueue(SensorManager,
+ Android->looper, LOOPER_ID_USER, NULL, NULL);
+
+ ANativeActivity_setWindowFlags(Android->activity,
+ AWINDOW_FLAG_KEEP_SCREEN_ON |
+ AWINDOW_FLAG_FULLSCREEN, 0);
+
+ createKeyMap();
+
+ // Create cursor control
+ CursorControl = new CCursorControl();
+
+ os::Printer::log("Waiting for Android activity window to be created.", ELL_DEBUG);
+
+ while ((!IsStarted || !IsFocused || IsPaused) && !IsClosing)
+ {
+ s32 events = 0;
+ android_poll_source* source = 0;
+
+ s32 id = ALooper_pollAll(-1, NULL, &events, (void**)&source);
+
+ if (id >=0 && source != NULL)
+ {
+ source->process(Android, source);
+ }
+ }
+
+ assert(Android->window);
+ os::Printer::log("Done", ELL_DEBUG);
+
+ ExposedVideoData.OGLESAndroid.Window = Android->window;
+
+ getVideoModeList();
+
+ createDriver();
+
+ if (VideoDriver)
+ createGUIAndScene();
+}
+
+
+CIrrDeviceAndroid::~CIrrDeviceAndroid()
+{
+ Android->userData = NULL;
+}
+
+video::IVideoModeList* CIrrDeviceAndroid::getVideoModeList()
+{
+ if (Android == NULL || Android->window == NULL)
+ return NULL;
+
+ core::dimension2d size = core::dimension2d(
+ ANativeWindow_getWidth(Android->window),
+ ANativeWindow_getHeight(Android->window));
+
+ CreationParams.WindowSize.Width = size.Width;
+ CreationParams.WindowSize.Height = size.Height;
+
+ if (!VideoModeList.getVideoModeCount())
+ {
+ VideoModeList.addMode(size, 32);
+ VideoModeList.setDesktop(32, size);
+ }
+
+ return &VideoModeList;
+}
+
+void CIrrDeviceAndroid::createDriver()
+{
+ // Create the driver.
+ switch(CreationParams.DriverType)
+ {
+ case video::EDT_OGLES1:
+ #ifdef _IRR_COMPILE_WITH_OGLES1_
+ VideoDriver = video::createOGLES1Driver(CreationParams, ExposedVideoData, FileSystem);
+ #else
+ os::Printer::log("No OpenGL ES 1.0 support compiled in.", ELL_ERROR);
+ #endif
+ break;
+
+ case video::EDT_OGLES2:
+ #ifdef _IRR_COMPILE_WITH_OGLES2_
+ VideoDriver = video::createOGLES2Driver(CreationParams, ExposedVideoData, FileSystem);
+ #else
+ os::Printer::log("No OpenGL ES 2.0 support compiled in.", ELL_ERROR);
+ #endif
+ break;
+
+ case video::EDT_NULL:
+ VideoDriver = video::createNullDriver(FileSystem, CreationParams.WindowSize);
+ break;
+
+ default:
+ os::Printer::log("Unable to create video driver of unknown type.", ELL_ERROR);
+ break;
+ }
+}
+
+bool CIrrDeviceAndroid::run()
+{
+ os::Timer::tick();
+
+ while (!IsClosing)
+ {
+ s32 Events = 0;
+ android_poll_source* Source = 0;
+ bool should_run = (IsStarted && IsFocused && !IsPaused) || IsClosing;
+ s32 id = ALooper_pollAll(should_run ? 0 : -1, NULL, &Events,
+ (void**)&Source);
+
+ if (id < 0)
+ break;
+
+ if (Source)
+ {
+ Source->process(Android, Source);
+ }
+
+ // if a sensor has data, we'll process it now.
+ if (id == LOOPER_ID_USER)
+ {
+ ASensorEvent event;
+ while (ASensorEventQueue_getEvents(SensorEventQueue, &event, 1) > 0)
+ {
+ switch (event.type)
+ {
+ case ASENSOR_TYPE_ACCELEROMETER:
+ SEvent accEvent;
+ accEvent.EventType = EET_ACCELEROMETER_EVENT;
+ accEvent.AccelerometerEvent.X = event.acceleration.x;
+ accEvent.AccelerometerEvent.Y = event.acceleration.y;
+ accEvent.AccelerometerEvent.Z = event.acceleration.z;
+
+ postEventFromUser(accEvent);
+ break;
+
+ case ASENSOR_TYPE_GYROSCOPE:
+ SEvent gyroEvent;
+ gyroEvent.EventType = EET_GYROSCOPE_EVENT;
+ gyroEvent.GyroscopeEvent.X = event.vector.x;
+ gyroEvent.GyroscopeEvent.Y = event.vector.y;
+ gyroEvent.GyroscopeEvent.Z = event.vector.z;
+
+ postEventFromUser(gyroEvent);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ return !IsClosing;
+}
+
+void CIrrDeviceAndroid::yield()
+{
+ struct timespec ts = {0,1};
+ nanosleep(&ts, NULL);
+}
+
+void CIrrDeviceAndroid::sleep(u32 timeMs, bool pauseTimer)
+{
+ const bool wasStopped = Timer ? Timer->isStopped() : true;
+
+ struct timespec ts;
+ ts.tv_sec = (time_t) (timeMs / 1000);
+ ts.tv_nsec = (long) (timeMs % 1000) * 1000000;
+
+ if (pauseTimer && !wasStopped)
+ Timer->stop();
+
+ nanosleep(&ts, NULL);
+
+ if (pauseTimer && !wasStopped)
+ Timer->start();
+}
+
+void CIrrDeviceAndroid::setWindowCaption(const wchar_t* text)
+{
+}
+
+bool CIrrDeviceAndroid::present(video::IImage* surface, void* windowId,
+ core::rect* srcClip)
+{
+ return true;
+}
+
+bool CIrrDeviceAndroid::isWindowActive() const
+{
+ return (IsFocused && !IsPaused);
+}
+
+bool CIrrDeviceAndroid::isWindowFocused() const
+{
+ return IsFocused;
+}
+
+bool CIrrDeviceAndroid::isWindowMinimized() const
+{
+ return IsPaused;
+}
+
+void CIrrDeviceAndroid::closeDevice()
+{
+}
+
+void CIrrDeviceAndroid::setResizable(bool resize)
+{
+}
+
+void CIrrDeviceAndroid::minimizeWindow()
+{
+}
+
+void CIrrDeviceAndroid::maximizeWindow()
+{
+}
+
+void CIrrDeviceAndroid::restoreWindow()
+{
+}
+
+E_DEVICE_TYPE CIrrDeviceAndroid::getType() const
+{
+ return EIDT_ANDROID;
+}
+
+void CIrrDeviceAndroid::handleAndroidCommand(android_app* app, int32_t cmd)
+{
+ CIrrDeviceAndroid* device = (CIrrDeviceAndroid *)app->userData;
+
+ switch (cmd)
+ {
+ case APP_CMD_SAVE_STATE:
+ os::Printer::log("Android command APP_CMD_SAVE_STATE", ELL_DEBUG);
+ break;
+ case APP_CMD_INIT_WINDOW:
+ os::Printer::log("Android command APP_CMD_INIT_WINDOW", ELL_DEBUG);
+
+ if (device != NULL)
+ {
+ device->getExposedVideoData().OGLESAndroid.Window = app->window;
+
+ // If the Android app is resumed, we need to re-create EGL surface
+ // to allow to draw something on it again.
+ if (device->VideoDriver != NULL &&
+ device->CreationParams.DriverType == video::EDT_OGLES2)
+ {
+ video::COGLES2Driver* driver = (video::COGLES2Driver*)(device->VideoDriver);
+ driver->reloadEGLSurface(app->window);
+ }
+ }
+
+ IsStarted = true;
+ break;
+ case APP_CMD_TERM_WINDOW:
+ os::Printer::log("Android command APP_CMD_TERM_WINDOW", ELL_DEBUG);
+ IsStarted = false;
+ break;
+ case APP_CMD_GAINED_FOCUS:
+ os::Printer::log("Android command APP_CMD_GAINED_FOCUS", ELL_DEBUG);
+ IsFocused = true;
+ break;
+ case APP_CMD_LOST_FOCUS:
+ os::Printer::log("Android command APP_CMD_LOST_FOCUS", ELL_DEBUG);
+ IsFocused = false;
+ break;
+ case APP_CMD_DESTROY:
+ os::Printer::log("Android command APP_CMD_DESTROY", ELL_DEBUG);
+ IsClosing = true;
+ // Make sure that state variables are set to the default state
+ // when the app is destroyed
+ IsPaused = true;
+ IsFocused = false;
+ IsStarted = false;
+ break;
+ case APP_CMD_PAUSE:
+ os::Printer::log("Android command APP_CMD_PAUSE", ELL_DEBUG);
+ IsPaused = true;
+ break;
+ case APP_CMD_RESUME:
+ os::Printer::log("Android command APP_CMD_RESUME", ELL_DEBUG);
+ IsPaused = false;
+ break;
+ case APP_CMD_START:
+ os::Printer::log("Android command APP_CMD_START", ELL_DEBUG);
+ break;
+ case APP_CMD_STOP:
+ os::Printer::log("Android command APP_CMD_STOP", ELL_DEBUG);
+ break;
+ case APP_CMD_WINDOW_RESIZED:
+ os::Printer::log("Android command APP_CMD_WINDOW_RESIZED", ELL_DEBUG);
+ break;
+ case APP_CMD_CONFIG_CHANGED:
+ os::Printer::log("Android command APP_CMD_CONFIG_CHANGED", ELL_DEBUG);
+ break;
+ case APP_CMD_LOW_MEMORY:
+ os::Printer::log("Android command APP_CMD_LOW_MEMORY", ELL_DEBUG);
+ break;
+ default:
+ break;
+ }
+
+ if (device != NULL)
+ {
+ SEvent event;
+ event.EventType = EET_SYSTEM_EVENT;
+ event.SystemEvent.EventType = ESET_ANDROID_CMD;
+ event.SystemEvent.AndroidCmd.Cmd = cmd;
+ device->postEventFromUser(event);
+ }
+}
+
+s32 CIrrDeviceAndroid::handleInput(android_app* app, AInputEvent* androidEvent)
+{
+ CIrrDeviceAndroid* device = (CIrrDeviceAndroid*)app->userData;
+ s32 status = 0;
+
+ if (device == NULL)
+ return status;
+
+
+ switch (AInputEvent_getType(androidEvent))
+ {
+ case AINPUT_EVENT_TYPE_MOTION:
+ {
+ SEvent event;
+ event.EventType = EET_TOUCH_INPUT_EVENT;
+
+ s32 eventAction = AMotionEvent_getAction(androidEvent);
+
+#if 0
+ // Useful for debugging. We might have to pass some of those infos on at some point.
+ // but preferably device independent (so iphone can use same irrlicht flags).
+ int32_t flags = AMotionEvent_getFlags(androidEvent);
+ os::Printer::log("flags: ", core::stringc(flags).c_str(), ELL_DEBUG);
+ int32_t metaState = AMotionEvent_getMetaState(androidEvent);
+ os::Printer::log("metaState: ", core::stringc(metaState).c_str(), ELL_DEBUG);
+ int32_t edgeFlags = AMotionEvent_getEdgeFlags(androidEvent);
+ os::Printer::log("edgeFlags: ", core::stringc(flags).c_str(), ELL_DEBUG);
+#endif
+
+ bool touchReceived = true;
+ bool simulate_mouse = false;
+ core::position2d mouse_pos = core::position2d(0, 0);
+
+ switch (eventAction & AMOTION_EVENT_ACTION_MASK)
+ {
+ case AMOTION_EVENT_ACTION_DOWN:
+ case AMOTION_EVENT_ACTION_POINTER_DOWN:
+ event.TouchInput.Event = ETIE_PRESSED_DOWN;
+ break;
+ case AMOTION_EVENT_ACTION_MOVE:
+ event.TouchInput.Event = ETIE_MOVED;
+ break;
+ case AMOTION_EVENT_ACTION_UP:
+ case AMOTION_EVENT_ACTION_POINTER_UP:
+ case AMOTION_EVENT_ACTION_CANCEL:
+ event.TouchInput.Event = ETIE_LEFT_UP;
+ break;
+ default:
+ touchReceived = false;
+ break;
+ }
+
+ if (touchReceived)
+ {
+ s32 count = 1;
+ s32 idx = (eventAction & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >>
+ AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
+
+ // Process all touches for move action.
+ if (event.TouchInput.Event == ETIE_MOVED)
+ {
+ count = AMotionEvent_getPointerCount(androidEvent);
+ idx = 0;
+ }
+
+ for (s32 i = 0; i < count; ++i)
+ {
+ event.TouchInput.ID = AMotionEvent_getPointerId(androidEvent, i + idx);
+ event.TouchInput.X = AMotionEvent_getX(androidEvent, i + idx);
+ event.TouchInput.Y = AMotionEvent_getY(androidEvent, i + idx);
+
+ device->postEventFromUser(event);
+
+ if (event.TouchInput.ID == 0)
+ {
+ simulate_mouse = true;
+ mouse_pos.X = event.TouchInput.X;
+ mouse_pos.Y = event.TouchInput.Y;
+ }
+ }
+
+ status = 1;
+ }
+
+ // Simulate mouse event for first finger on multitouch device.
+ // This allows to click on GUI elements.
+ if (simulate_mouse)
+ {
+ device->CursorControl->setPosition(mouse_pos);
+
+ SEvent irrevent;
+ bool send_event = true;
+
+ switch (event.TouchInput.Event)
+ {
+ case ETIE_PRESSED_DOWN:
+ irrevent.MouseInput.Event = EMIE_LMOUSE_PRESSED_DOWN;
+ device->IsMousePressed = true;
+ break;
+ case ETIE_LEFT_UP:
+ irrevent.MouseInput.Event = EMIE_LMOUSE_LEFT_UP;
+ device->IsMousePressed = false;
+ break;
+ case ETIE_MOVED:
+ irrevent.MouseInput.Event = EMIE_MOUSE_MOVED;
+ break;
+ default:
+ send_event = false;
+ break;
+ }
+
+ if (send_event)
+ {
+ irrevent.MouseInput.Control = false;
+ irrevent.MouseInput.Shift = false;
+ irrevent.MouseInput.ButtonStates = device->IsMousePressed ?
+ irr::EMBSM_LEFT : 0;
+ irrevent.EventType = EET_MOUSE_INPUT_EVENT;
+ irrevent.MouseInput.X = mouse_pos.X;
+ irrevent.MouseInput.Y = mouse_pos.Y;
+
+ device->postEventFromUser(irrevent);
+ }
+ }
+
+ break;
+ }
+ case AINPUT_EVENT_TYPE_KEY:
+ {
+ bool ignore_event = false;
+
+ SEvent event;
+ event.EventType = EET_KEY_INPUT_EVENT;
+ event.KeyInput.Char = 0;
+ event.KeyInput.PressedDown = false;
+ event.KeyInput.Key = KEY_UNKNOWN;
+
+ int32_t keyCode = AKeyEvent_getKeyCode(androidEvent);
+ int32_t keyAction = AKeyEvent_getAction(androidEvent);
+ int32_t keyMetaState = AKeyEvent_getMetaState(androidEvent);
+ int32_t keyRepeat = AKeyEvent_getRepeatCount(androidEvent);
+
+ if (keyAction == AKEY_EVENT_ACTION_DOWN)
+ {
+ event.KeyInput.PressedDown = true;
+ }
+ else if (keyAction == AKEY_EVENT_ACTION_UP)
+ {
+ event.KeyInput.PressedDown = false;
+ }
+ else if (keyAction == AKEY_EVENT_ACTION_MULTIPLE)
+ {
+ // TODO: Multiple duplicate key events have occurred in a row,
+ // or a complex string is being delivered. The repeat_count
+ // property of the key event contains the number of times the
+ // given key code should be executed.
+ // I guess this might necessary for more complicated i18n key input,
+ // but don't see yet how to handle this correctly.
+ }
+
+ event.KeyInput.Shift = (keyMetaState & AMETA_SHIFT_ON ||
+ keyMetaState & AMETA_SHIFT_LEFT_ON ||
+ keyMetaState & AMETA_SHIFT_RIGHT_ON);
+
+ event.KeyInput.Control = (keyMetaState & AMETA_CTRL_ON ||
+ keyMetaState & AMETA_CTRL_LEFT_ON ||
+ keyMetaState & AMETA_CTRL_RIGHT_ON);
+
+ event.KeyInput.SystemKeyCode = (u32)keyCode;
+
+ if (keyCode >= 0 && (u32)keyCode < device->KeyMap.size())
+ {
+ event.KeyInput.Key = device->KeyMap[keyCode];
+ }
+
+ if (event.KeyInput.Key > 0)
+ {
+ device->getKeyChar(event);
+ }
+
+ // Handle an event when back button in pressed just like an escape key
+ // and also avoid repeating the event to avoid some strange behaviour
+ if (event.KeyInput.SystemKeyCode == AKEYCODE_BACK)
+ {
+ status = 1;
+
+ if (event.KeyInput.PressedDown == false || keyRepeat > 0)
+ {
+ ignore_event = true;
+ }
+ }
+
+ // Mark escape key event as handled by application to avoid receiving
+ // AKEYCODE_BACK key event
+ if (event.KeyInput.SystemKeyCode == AKEYCODE_ESCAPE)
+ {
+ status = 1;
+ }
+
+ if (!ignore_event)
+ {
+ device->postEventFromUser(event);
+ }
+
+ break;
+ }
+ default:
+ break;
+ }
+
+ return status;
+}
+
+video::SExposedVideoData& CIrrDeviceAndroid::getExposedVideoData()
+{
+ return ExposedVideoData;
+}
+
+void CIrrDeviceAndroid::createKeyMap()
+{
+ KeyMap.set_used(223);
+
+ KeyMap[0] = KEY_UNKNOWN; // AKEYCODE_UNKNOWN
+ KeyMap[1] = KEY_LBUTTON; // AKEYCODE_SOFT_LEFT
+ KeyMap[2] = KEY_RBUTTON; // AKEYCODE_SOFT_RIGHT
+ KeyMap[3] = KEY_HOME; // AKEYCODE_HOME
+ KeyMap[4] = KEY_ESCAPE; // AKEYCODE_BACK
+ KeyMap[5] = KEY_UNKNOWN; // AKEYCODE_CALL
+ KeyMap[6] = KEY_UNKNOWN; // AKEYCODE_ENDCALL
+ KeyMap[7] = KEY_KEY_0; // AKEYCODE_0
+ KeyMap[8] = KEY_KEY_1; // AKEYCODE_1
+ KeyMap[9] = KEY_KEY_2; // AKEYCODE_2
+ KeyMap[10] = KEY_KEY_3; // AKEYCODE_3
+ KeyMap[11] = KEY_KEY_4; // AKEYCODE_4
+ KeyMap[12] = KEY_KEY_5; // AKEYCODE_5
+ KeyMap[13] = KEY_KEY_6; // AKEYCODE_6
+ KeyMap[14] = KEY_KEY_7; // AKEYCODE_7
+ KeyMap[15] = KEY_KEY_8; // AKEYCODE_8
+ KeyMap[16] = KEY_KEY_9; // AKEYCODE_9
+ KeyMap[17] = KEY_UNKNOWN; // AKEYCODE_STAR
+ KeyMap[18] = KEY_UNKNOWN; // AKEYCODE_POUND
+ KeyMap[19] = KEY_UP; // AKEYCODE_DPAD_UP
+ KeyMap[20] = KEY_DOWN; // AKEYCODE_DPAD_DOWN
+ KeyMap[21] = KEY_LEFT; // AKEYCODE_DPAD_LEFT
+ KeyMap[22] = KEY_RIGHT; // AKEYCODE_DPAD_RIGHT
+ KeyMap[23] = KEY_SELECT; // AKEYCODE_DPAD_CENTER
+ KeyMap[24] = KEY_VOLUME_DOWN; // AKEYCODE_VOLUME_UP
+ KeyMap[25] = KEY_VOLUME_UP; // AKEYCODE_VOLUME_DOWN
+ KeyMap[26] = KEY_UNKNOWN; // AKEYCODE_POWER
+ KeyMap[27] = KEY_UNKNOWN; // AKEYCODE_CAMERA
+ KeyMap[28] = KEY_CLEAR; // AKEYCODE_CLEAR
+ KeyMap[29] = KEY_KEY_A; // AKEYCODE_A
+ KeyMap[30] = KEY_KEY_B; // AKEYCODE_B
+ KeyMap[31] = KEY_KEY_C; // AKEYCODE_C
+ KeyMap[32] = KEY_KEY_D; // AKEYCODE_D
+ KeyMap[33] = KEY_KEY_E; // AKEYCODE_E
+ KeyMap[34] = KEY_KEY_F; // AKEYCODE_F
+ KeyMap[35] = KEY_KEY_G; // AKEYCODE_G
+ KeyMap[36] = KEY_KEY_H; // AKEYCODE_H
+ KeyMap[37] = KEY_KEY_I; // AKEYCODE_I
+ KeyMap[38] = KEY_KEY_J; // AKEYCODE_J
+ KeyMap[39] = KEY_KEY_K; // AKEYCODE_K
+ KeyMap[40] = KEY_KEY_L; // AKEYCODE_L
+ KeyMap[41] = KEY_KEY_M; // AKEYCODE_M
+ KeyMap[42] = KEY_KEY_N; // AKEYCODE_N
+ KeyMap[43] = KEY_KEY_O; // AKEYCODE_O
+ KeyMap[44] = KEY_KEY_P; // AKEYCODE_P
+ KeyMap[45] = KEY_KEY_Q; // AKEYCODE_Q
+ KeyMap[46] = KEY_KEY_R; // AKEYCODE_R
+ KeyMap[47] = KEY_KEY_S; // AKEYCODE_S
+ KeyMap[48] = KEY_KEY_T; // AKEYCODE_T
+ KeyMap[49] = KEY_KEY_U; // AKEYCODE_U
+ KeyMap[50] = KEY_KEY_V; // AKEYCODE_V
+ KeyMap[51] = KEY_KEY_W; // AKEYCODE_W
+ KeyMap[52] = KEY_KEY_X; // AKEYCODE_X
+ KeyMap[53] = KEY_KEY_Y; // AKEYCODE_Y
+ KeyMap[54] = KEY_KEY_Z; // AKEYCODE_Z
+ KeyMap[55] = KEY_COMMA; // AKEYCODE_COMMA
+ KeyMap[56] = KEY_PERIOD; // AKEYCODE_PERIOD
+ KeyMap[57] = KEY_MENU; // AKEYCODE_ALT_LEFT
+ KeyMap[58] = KEY_MENU; // AKEYCODE_ALT_RIGHT
+ KeyMap[59] = KEY_LSHIFT; // AKEYCODE_SHIFT_LEFT
+ KeyMap[60] = KEY_RSHIFT; // AKEYCODE_SHIFT_RIGHT
+ KeyMap[61] = KEY_TAB; // AKEYCODE_TAB
+ KeyMap[62] = KEY_SPACE; // AKEYCODE_SPACE
+ KeyMap[63] = KEY_UNKNOWN; // AKEYCODE_SYM
+ KeyMap[64] = KEY_UNKNOWN; // AKEYCODE_EXPLORER
+ KeyMap[65] = KEY_UNKNOWN; // AKEYCODE_ENVELOPE
+ KeyMap[66] = KEY_RETURN; // AKEYCODE_ENTER
+ KeyMap[67] = KEY_BACK; // AKEYCODE_DEL
+ KeyMap[68] = KEY_OEM_3; // AKEYCODE_GRAVE
+ KeyMap[69] = KEY_MINUS; // AKEYCODE_MINUS
+ KeyMap[70] = KEY_UNKNOWN; // AKEYCODE_EQUALS
+ KeyMap[71] = KEY_UNKNOWN; // AKEYCODE_LEFT_BRACKET
+ KeyMap[72] = KEY_UNKNOWN; // AKEYCODE_RIGHT_BRACKET
+ KeyMap[73] = KEY_UNKNOWN; // AKEYCODE_BACKSLASH
+ KeyMap[74] = KEY_UNKNOWN; // AKEYCODE_SEMICOLON
+ KeyMap[75] = KEY_UNKNOWN; // AKEYCODE_APOSTROPHE
+ KeyMap[76] = KEY_UNKNOWN; // AKEYCODE_SLASH
+ KeyMap[77] = KEY_UNKNOWN; // AKEYCODE_AT
+ KeyMap[78] = KEY_UNKNOWN; // AKEYCODE_NUM
+ KeyMap[79] = KEY_UNKNOWN; // AKEYCODE_HEADSETHOOK
+ KeyMap[80] = KEY_UNKNOWN; // AKEYCODE_FOCUS (*Camera* focus)
+ KeyMap[81] = KEY_PLUS; // AKEYCODE_PLUS
+ KeyMap[82] = KEY_MENU; // AKEYCODE_MENU
+ KeyMap[83] = KEY_UNKNOWN; // AKEYCODE_NOTIFICATION
+ KeyMap[84] = KEY_UNKNOWN; // AKEYCODE_SEARCH
+ KeyMap[85] = KEY_MEDIA_PLAY_PAUSE; // AKEYCODE_MEDIA_PLAY_PAUSE
+ KeyMap[86] = KEY_MEDIA_STOP; // AKEYCODE_MEDIA_STOP
+ KeyMap[87] = KEY_MEDIA_NEXT_TRACK; // AKEYCODE_MEDIA_NEXT
+ KeyMap[88] = KEY_MEDIA_PREV_TRACK; // AKEYCODE_MEDIA_PREVIOUS
+ KeyMap[89] = KEY_UNKNOWN; // AKEYCODE_MEDIA_REWIND
+ KeyMap[90] = KEY_UNKNOWN; // AKEYCODE_MEDIA_FAST_FORWARD
+ KeyMap[91] = KEY_VOLUME_MUTE; // AKEYCODE_MUTE
+ KeyMap[92] = KEY_PRIOR; // AKEYCODE_PAGE_UP
+ KeyMap[93] = KEY_NEXT; // AKEYCODE_PAGE_DOWN
+ KeyMap[94] = KEY_UNKNOWN; // AKEYCODE_PICTSYMBOLS
+ KeyMap[95] = KEY_UNKNOWN; // AKEYCODE_SWITCH_CHARSET
+
+ // following look like controller inputs
+ KeyMap[96] = KEY_UNKNOWN; // AKEYCODE_BUTTON_A
+ KeyMap[97] = KEY_UNKNOWN; // AKEYCODE_BUTTON_B
+ KeyMap[98] = KEY_UNKNOWN; // AKEYCODE_BUTTON_C
+ KeyMap[99] = KEY_UNKNOWN; // AKEYCODE_BUTTON_X
+ KeyMap[100] = KEY_UNKNOWN; // AKEYCODE_BUTTON_Y
+ KeyMap[101] = KEY_UNKNOWN; // AKEYCODE_BUTTON_Z
+ KeyMap[102] = KEY_UNKNOWN; // AKEYCODE_BUTTON_L1
+ KeyMap[103] = KEY_UNKNOWN; // AKEYCODE_BUTTON_R1
+ KeyMap[104] = KEY_UNKNOWN; // AKEYCODE_BUTTON_L2
+ KeyMap[105] = KEY_UNKNOWN; // AKEYCODE_BUTTON_R2
+ KeyMap[106] = KEY_UNKNOWN; // AKEYCODE_BUTTON_THUMBL
+ KeyMap[107] = KEY_UNKNOWN; // AKEYCODE_BUTTON_THUMBR
+ KeyMap[108] = KEY_UNKNOWN; // AKEYCODE_BUTTON_START
+ KeyMap[109] = KEY_UNKNOWN; // AKEYCODE_BUTTON_SELECT
+ KeyMap[110] = KEY_UNKNOWN; // AKEYCODE_BUTTON_MODE
+
+ KeyMap[111] = KEY_ESCAPE; // AKEYCODE_ESCAPE
+ KeyMap[112] = KEY_DELETE; // AKEYCODE_FORWARD_DEL
+ KeyMap[113] = KEY_CONTROL; // AKEYCODE_CTRL_LEFT
+ KeyMap[114] = KEY_CONTROL; // AKEYCODE_CTRL_RIGHT
+ KeyMap[115] = KEY_CAPITAL; // AKEYCODE_CAPS_LOCK
+ KeyMap[116] = KEY_SCROLL; // AKEYCODE_SCROLL_LOCK
+ KeyMap[117] = KEY_UNKNOWN; // AKEYCODE_META_LEFT
+ KeyMap[118] = KEY_UNKNOWN; // AKEYCODE_META_RIGHT
+ KeyMap[119] = KEY_UNKNOWN; // AKEYCODE_FUNCTION
+ KeyMap[120] = KEY_SNAPSHOT; // AKEYCODE_SYSRQ
+ KeyMap[121] = KEY_PAUSE; // AKEYCODE_BREAK
+ KeyMap[122] = KEY_HOME; // AKEYCODE_MOVE_HOME
+ KeyMap[123] = KEY_END; // AKEYCODE_MOVE_END
+ KeyMap[124] = KEY_INSERT; // AKEYCODE_INSERT
+ KeyMap[125] = KEY_UNKNOWN; // AKEYCODE_FORWARD
+ KeyMap[126] = KEY_PLAY; // AKEYCODE_MEDIA_PLAY
+ KeyMap[127] = KEY_MEDIA_PLAY_PAUSE; // AKEYCODE_MEDIA_PAUSE
+ KeyMap[128] = KEY_UNKNOWN; // AKEYCODE_MEDIA_CLOSE
+ KeyMap[129] = KEY_UNKNOWN; // AKEYCODE_MEDIA_EJECT
+ KeyMap[130] = KEY_UNKNOWN; // AKEYCODE_MEDIA_RECORD
+ KeyMap[131] = KEY_F1; // AKEYCODE_F1
+ KeyMap[132] = KEY_F2; // AKEYCODE_F2
+ KeyMap[133] = KEY_F3; // AKEYCODE_F3
+ KeyMap[134] = KEY_F4; // AKEYCODE_F4
+ KeyMap[135] = KEY_F5; // AKEYCODE_F5
+ KeyMap[136] = KEY_F6; // AKEYCODE_F6
+ KeyMap[137] = KEY_F7; // AKEYCODE_F7
+ KeyMap[138] = KEY_F8; // AKEYCODE_F8
+ KeyMap[139] = KEY_F9; // AKEYCODE_F9
+ KeyMap[140] = KEY_F10; // AKEYCODE_F10
+ KeyMap[141] = KEY_F11; // AKEYCODE_F11
+ KeyMap[142] = KEY_F12; // AKEYCODE_F12
+ KeyMap[143] = KEY_NUMLOCK; // AKEYCODE_NUM_LOCK
+ KeyMap[144] = KEY_NUMPAD0; // AKEYCODE_NUMPAD_0
+ KeyMap[145] = KEY_NUMPAD1; // AKEYCODE_NUMPAD_1
+ KeyMap[146] = KEY_NUMPAD2; // AKEYCODE_NUMPAD_2
+ KeyMap[147] = KEY_NUMPAD3; // AKEYCODE_NUMPAD_3
+ KeyMap[148] = KEY_NUMPAD4; // AKEYCODE_NUMPAD_4
+ KeyMap[149] = KEY_NUMPAD5; // AKEYCODE_NUMPAD_5
+ KeyMap[150] = KEY_NUMPAD6; // AKEYCODE_NUMPAD_6
+ KeyMap[151] = KEY_NUMPAD7; // AKEYCODE_NUMPAD_7
+ KeyMap[152] = KEY_NUMPAD8; // AKEYCODE_NUMPAD_8
+ KeyMap[153] = KEY_NUMPAD9; // AKEYCODE_NUMPAD_9
+ KeyMap[154] = KEY_DIVIDE; // AKEYCODE_NUMPAD_DIVIDE
+ KeyMap[155] = KEY_MULTIPLY; // AKEYCODE_NUMPAD_MULTIPLY
+ KeyMap[156] = KEY_SUBTRACT; // AKEYCODE_NUMPAD_SUBTRACT
+ KeyMap[157] = KEY_ADD; // AKEYCODE_NUMPAD_ADD
+ KeyMap[158] = KEY_UNKNOWN; // AKEYCODE_NUMPAD_DOT
+ KeyMap[159] = KEY_COMMA; // AKEYCODE_NUMPAD_COMMA
+ KeyMap[160] = KEY_RETURN; // AKEYCODE_NUMPAD_ENTER
+ KeyMap[161] = KEY_UNKNOWN; // AKEYCODE_NUMPAD_EQUALS
+ KeyMap[162] = KEY_UNKNOWN; // AKEYCODE_NUMPAD_LEFT_PAREN
+ KeyMap[163] = KEY_UNKNOWN; // AKEYCODE_NUMPAD_RIGHT_PAREN
+ KeyMap[164] = KEY_VOLUME_MUTE; // AKEYCODE_VOLUME_MUTE
+ KeyMap[165] = KEY_UNKNOWN; // AKEYCODE_INFO
+ KeyMap[166] = KEY_UNKNOWN; // AKEYCODE_CHANNEL_UP
+ KeyMap[167] = KEY_UNKNOWN; // AKEYCODE_CHANNEL_DOWN
+ KeyMap[168] = KEY_ZOOM; // AKEYCODE_ZOOM_IN
+ KeyMap[169] = KEY_UNKNOWN; // AKEYCODE_ZOOM_OUT
+ KeyMap[170] = KEY_UNKNOWN; // AKEYCODE_TV
+ KeyMap[171] = KEY_UNKNOWN; // AKEYCODE_WINDOW
+ KeyMap[172] = KEY_UNKNOWN; // AKEYCODE_GUIDE
+ KeyMap[173] = KEY_UNKNOWN; // AKEYCODE_DVR
+ KeyMap[174] = KEY_UNKNOWN; // AKEYCODE_BOOKMARK
+ KeyMap[175] = KEY_UNKNOWN; // AKEYCODE_CAPTIONS
+ KeyMap[176] = KEY_UNKNOWN; // AKEYCODE_SETTINGS
+ KeyMap[177] = KEY_UNKNOWN; // AKEYCODE_TV_POWER
+ KeyMap[178] = KEY_UNKNOWN; // AKEYCODE_TV_INPUT
+ KeyMap[179] = KEY_UNKNOWN; // AKEYCODE_STB_POWER
+ KeyMap[180] = KEY_UNKNOWN; // AKEYCODE_STB_INPUT
+ KeyMap[181] = KEY_UNKNOWN; // AKEYCODE_AVR_POWER
+ KeyMap[182] = KEY_UNKNOWN; // AKEYCODE_AVR_INPUT
+ KeyMap[183] = KEY_UNKNOWN; // AKEYCODE_PROG_RED
+ KeyMap[184] = KEY_UNKNOWN; // AKEYCODE_PROG_GREEN
+ KeyMap[185] = KEY_UNKNOWN; // AKEYCODE_PROG_YELLOW
+ KeyMap[186] = KEY_UNKNOWN; // AKEYCODE_PROG_BLUE
+ KeyMap[187] = KEY_UNKNOWN; // AKEYCODE_APP_SWITCH
+ KeyMap[188] = KEY_UNKNOWN; // AKEYCODE_BUTTON_1
+ KeyMap[189] = KEY_UNKNOWN; // AKEYCODE_BUTTON_2
+ KeyMap[190] = KEY_UNKNOWN; // AKEYCODE_BUTTON_3
+ KeyMap[191] = KEY_UNKNOWN; // AKEYCODE_BUTTON_4
+ KeyMap[192] = KEY_UNKNOWN; // AKEYCODE_BUTTON_5
+ KeyMap[193] = KEY_UNKNOWN; // AKEYCODE_BUTTON_6
+ KeyMap[194] = KEY_UNKNOWN; // AKEYCODE_BUTTON_7
+ KeyMap[195] = KEY_UNKNOWN; // AKEYCODE_BUTTON_8
+ KeyMap[196] = KEY_UNKNOWN; // AKEYCODE_BUTTON_9
+ KeyMap[197] = KEY_UNKNOWN; // AKEYCODE_BUTTON_10
+ KeyMap[198] = KEY_UNKNOWN; // AKEYCODE_BUTTON_11
+ KeyMap[199] = KEY_UNKNOWN; // AKEYCODE_BUTTON_12
+ KeyMap[200] = KEY_UNKNOWN; // AKEYCODE_BUTTON_13
+ KeyMap[201] = KEY_UNKNOWN; // AKEYCODE_BUTTON_14
+ KeyMap[202] = KEY_UNKNOWN; // AKEYCODE_BUTTON_15
+ KeyMap[203] = KEY_UNKNOWN; // AKEYCODE_BUTTON_16
+ KeyMap[204] = KEY_UNKNOWN; // AKEYCODE_LANGUAGE_SWITCH
+ KeyMap[205] = KEY_UNKNOWN; // AKEYCODE_MANNER_MODE
+ KeyMap[206] = KEY_UNKNOWN; // AKEYCODE_3D_MODE
+ KeyMap[207] = KEY_UNKNOWN; // AKEYCODE_CONTACTS
+ KeyMap[208] = KEY_UNKNOWN; // AKEYCODE_CALENDAR
+ KeyMap[209] = KEY_UNKNOWN; // AKEYCODE_MUSIC
+ KeyMap[210] = KEY_UNKNOWN; // AKEYCODE_CALCULATOR
+ KeyMap[211] = KEY_UNKNOWN; // AKEYCODE_ZENKAKU_HANKAKU
+ KeyMap[212] = KEY_UNKNOWN; // AKEYCODE_EISU
+ KeyMap[213] = KEY_UNKNOWN; // AKEYCODE_MUHENKAN
+ KeyMap[214] = KEY_UNKNOWN; // AKEYCODE_HENKAN
+ KeyMap[215] = KEY_UNKNOWN; // AKEYCODE_KATAKANA_HIRAGANA
+ KeyMap[216] = KEY_UNKNOWN; // AKEYCODE_YEN
+ KeyMap[217] = KEY_UNKNOWN; // AKEYCODE_RO
+ KeyMap[218] = KEY_UNKNOWN; // AKEYCODE_KANA
+ KeyMap[219] = KEY_UNKNOWN; // AKEYCODE_ASSIST
+ KeyMap[220] = KEY_UNKNOWN; // AKEYCODE_BRIGHTNESS_DOWN
+ KeyMap[221] = KEY_UNKNOWN; // AKEYCODE_BRIGHTNESS_UP ,
+ KeyMap[222] = KEY_UNKNOWN; // AKEYCODE_MEDIA_AUDIO_TRACK
+}
+
+void CIrrDeviceAndroid::getKeyChar(SEvent& event)
+{
+ // Handle ASCII chars
+
+ event.KeyInput.Char = 0;
+
+ // A-Z
+ if (event.KeyInput.SystemKeyCode > 28 && event.KeyInput.SystemKeyCode < 55)
+ {
+ if (event.KeyInput.Shift)
+ {
+ event.KeyInput.Char = event.KeyInput.SystemKeyCode + 36;
+ }
+ else
+ {
+ event.KeyInput.Char = event.KeyInput.SystemKeyCode + 68;
+ }
+ }
+
+ // 0-9
+ else if (event.KeyInput.SystemKeyCode > 6 && event.KeyInput.SystemKeyCode < 17)
+ {
+ event.KeyInput.Char = event.KeyInput.SystemKeyCode + 41;
+ }
+
+ else if (event.KeyInput.SystemKeyCode == AKEYCODE_BACK)
+ {
+ event.KeyInput.Char = 8;
+ }
+ else if (event.KeyInput.SystemKeyCode == AKEYCODE_DEL)
+ {
+ event.KeyInput.Char = 8;
+ }
+ else if (event.KeyInput.SystemKeyCode == AKEYCODE_TAB)
+ {
+ event.KeyInput.Char = 9;
+ }
+ else if (event.KeyInput.SystemKeyCode == AKEYCODE_ENTER)
+ {
+ event.KeyInput.Char = 13;
+ }
+ else if (event.KeyInput.SystemKeyCode == AKEYCODE_SPACE)
+ {
+ event.KeyInput.Char = 32;
+ }
+ else if (event.KeyInput.SystemKeyCode == AKEYCODE_COMMA)
+ {
+ event.KeyInput.Char = 44;
+ }
+ else if (event.KeyInput.SystemKeyCode == AKEYCODE_PERIOD)
+ {
+ event.KeyInput.Char = 46;
+ }
+}
+
+bool CIrrDeviceAndroid::activateAccelerometer(float updateInterval)
+{
+ if (!isAccelerometerAvailable())
+ return false;
+
+ ASensorEventQueue_enableSensor(SensorEventQueue, Accelerometer);
+ ASensorEventQueue_setEventRate(SensorEventQueue, Accelerometer,
+ (int32_t)(updateInterval*1000.f*1000.f)); // in microseconds
+
+ os::Printer::log("Activated accelerometer", ELL_DEBUG);
+ return true;
+}
+
+bool CIrrDeviceAndroid::deactivateAccelerometer()
+{
+ if (!Accelerometer)
+ return false;
+
+ ASensorEventQueue_disableSensor(SensorEventQueue, Accelerometer);
+ Accelerometer = 0;
+ os::Printer::log("Deactivated accelerometer", ELL_DEBUG);
+ return true;
+}
+
+bool CIrrDeviceAndroid::isAccelerometerActive()
+{
+ return (Accelerometer != NULL);
+}
+
+bool CIrrDeviceAndroid::isAccelerometerAvailable()
+{
+ if (!Accelerometer)
+ {
+ Accelerometer = ASensorManager_getDefaultSensor(SensorManager,
+ ASENSOR_TYPE_ACCELEROMETER);
+ }
+
+ return (Accelerometer != NULL);
+}
+
+bool CIrrDeviceAndroid::activateGyroscope(float updateInterval)
+{
+ if (!isGyroscopeAvailable())
+ return false;
+
+ ASensorEventQueue_enableSensor(SensorEventQueue, Gyroscope);
+ ASensorEventQueue_setEventRate(SensorEventQueue, Gyroscope,
+ (int32_t)(updateInterval*1000.f*1000.f)); // in microseconds
+
+ os::Printer::log("Activated gyroscope", ELL_DEBUG);
+ return true;
+}
+
+bool CIrrDeviceAndroid::deactivateGyroscope()
+{
+ if (!Gyroscope)
+ return false;
+
+ ASensorEventQueue_disableSensor(SensorEventQueue, Gyroscope);
+ Gyroscope = 0;
+ os::Printer::log("Deactivated gyroscope", ELL_DEBUG);
+ return true;
+}
+
+bool CIrrDeviceAndroid::isGyroscopeActive()
+{
+ return (Gyroscope != NULL);
+}
+
+bool CIrrDeviceAndroid::isGyroscopeAvailable()
+{
+ if (!Gyroscope)
+ {
+ Gyroscope = ASensorManager_getDefaultSensor(SensorManager,
+ ASENSOR_TYPE_GYROSCOPE);
+ }
+
+ return (Gyroscope != NULL);
+}
+
+
+} // end namespace irr
+
+#endif
+
+
diff --git a/lib/irrlicht/source/Irrlicht/CIrrDeviceAndroid.h b/lib/irrlicht/source/Irrlicht/CIrrDeviceAndroid.h
new file mode 100644
index 000000000..86d3ce1fe
--- /dev/null
+++ b/lib/irrlicht/source/Irrlicht/CIrrDeviceAndroid.h
@@ -0,0 +1,126 @@
+// Copyright (C) 2002-2011 Nikolaus Gebhardt
+// Copyright (C) 2016-2017 Dawid Gan
+// This file is part of the "Irrlicht Engine".
+// For conditions of distribution and use, see copyright notice in irrlicht.h
+
+#ifndef __C_IRR_DEVICE_ANDROID_H_INCLUDED__
+#define __C_IRR_DEVICE_ANDROID_H_INCLUDED__
+
+#include "IrrCompileConfig.h"
+
+#ifdef _IRR_COMPILE_WITH_ANDROID_DEVICE_
+
+#include
+#include
+#include
+#include "CIrrDeviceStub.h"
+#include "IrrlichtDevice.h"
+#include "IImagePresenter.h"
+#include "ICursorControl.h"
+
+
+namespace irr
+{
+
+ class CIrrDeviceAndroid : public CIrrDeviceStub, video::IImagePresenter
+ {
+ public:
+ //! constructor
+ CIrrDeviceAndroid(const SIrrlichtCreationParameters& param);
+
+ //! destructor
+ virtual ~CIrrDeviceAndroid();
+
+ virtual bool run();
+ virtual void yield();
+ virtual void sleep(u32 timeMs, bool pauseTimer=false);
+ virtual void setWindowCaption(const wchar_t* text);
+ virtual bool present(video::IImage* surface, void* windowId, core::rect* srcClip);
+ virtual bool isWindowActive() const;
+ virtual bool isWindowFocused() const;
+ virtual bool isWindowMinimized() const;
+ virtual void closeDevice();
+ virtual void setResizable( bool resize=false );
+ virtual void minimizeWindow();
+ virtual void maximizeWindow();
+ virtual void restoreWindow();
+ virtual E_DEVICE_TYPE getType() const;
+ virtual bool activateAccelerometer(float updateInterval);
+ virtual bool deactivateAccelerometer();
+ virtual bool isAccelerometerActive();
+ virtual bool isAccelerometerAvailable();
+ virtual bool activateGyroscope(float updateInterval);
+ virtual bool deactivateGyroscope();
+ virtual bool isGyroscopeActive();
+ virtual bool isGyroscopeAvailable();
+ video::IVideoModeList* getVideoModeList();
+
+ class CCursorControl : public gui::ICursorControl
+ {
+ public:
+
+ CCursorControl() : CursorPos(core::position2d(0, 0)) {}
+ virtual void setVisible(bool visible) {}
+ virtual bool isVisible() const {return false;}
+ virtual void setPosition(const core::position2d &pos)
+ {
+ setPosition(pos.X, pos.Y);
+ }
+ virtual void setPosition(f32 x, f32 y)
+ {
+ CursorPos.X = x;
+ CursorPos.Y = y;
+ }
+ virtual void setPosition(const core::position2d &pos)
+ {
+ setPosition(pos.X, pos.Y);
+ }
+ virtual void setPosition(s32 x, s32 y)
+ {
+ CursorPos.X = x;
+ CursorPos.Y = y;
+ }
+ virtual const core::position2d& getPosition()
+ {
+ return CursorPos;
+ }
+ virtual core::position2d getRelativePosition()
+ {
+ return core::position2d(0, 0);
+ }
+ virtual void setReferenceRect(core::rect* rect=0) {}
+ private:
+ core::position2d CursorPos;
+ };
+
+ private:
+ android_app* Android;
+ ASensorManager* SensorManager;
+ ASensorEventQueue* SensorEventQueue;
+ const ASensor* Accelerometer;
+ const ASensor* Gyroscope;
+
+ static bool IsPaused;
+ static bool IsFocused;
+ static bool IsStarted;
+ static bool IsClosing;
+
+ bool IsMousePressed;
+
+ video::SExposedVideoData ExposedVideoData;
+
+ core::array KeyMap;
+
+ void createDriver();
+ void createKeyMap();
+ void getKeyChar(SEvent& event);
+ video::SExposedVideoData& getExposedVideoData();
+
+ static void handleAndroidCommand(android_app* app, int32_t cmd);
+ static s32 handleInput(android_app* app, AInputEvent* event);
+ };
+
+} // end namespace irr
+
+#endif // _IRR_COMPILE_WITH_ANDROID_DEVICE_
+#endif // __C_IRR_DEVICE_ANDROID_H_INCLUDED__
diff --git a/lib/libpng/CMakeLists.txt b/lib/libpng/CMakeLists.txt
index b8d476b12..68aa98cac 100644
--- a/lib/libpng/CMakeLists.txt
+++ b/lib/libpng/CMakeLists.txt
@@ -60,6 +60,7 @@ if(NOT WIN32)
if(NOT M_LIBRARY)
message(STATUS
"math library 'libm' not found - floating point support disabled")
+ set(M_LIBRARY "")
endif()
else()
# not needed on windows
diff --git a/sources.cmake b/sources.cmake
index d4f28ae4d..ddc029d4f 100644
--- a/sources.cmake
+++ b/sources.cmake
@@ -1,4 +1,4 @@
-# Modify this file to change the last-modified date when you add/remove a file.
+# Modify this file to change the last-modified date when you add/remove a file.
# This will then trigger a new cmake run automatically.
file(GLOB_RECURSE STK_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.hpp")
file(GLOB_RECURSE STK_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.cpp")
diff --git a/src/io/assets_android.cpp b/src/io/assets_android.cpp
new file mode 100644
index 000000000..11f6f70fd
--- /dev/null
+++ b/src/io/assets_android.cpp
@@ -0,0 +1,433 @@
+// SuperTuxKart - a fun racing game with go-kart
+// Copyright (C) 2014-2015 SuperTuxKart-Team
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 3
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+#include "graphics/irr_driver.hpp"
+#include "io/assets_android.hpp"
+#include "io/file_manager.hpp"
+#include "utils/log.hpp"
+#include "utils/progress_bar_android.hpp"
+
+#include
+#include
+#include
+
+#ifdef ANDROID
+#include
+#endif
+
+//-----------------------------------------------------------------------------
+/** Assets Android constructor
+ */
+AssetsAndroid::AssetsAndroid(FileManager* file_manager)
+{
+ m_file_manager = file_manager;
+}
+
+//-----------------------------------------------------------------------------
+/** A function that detects a path where data directory is placed and that
+ * sets some environment variables that are used for finding data and
+ * home directory in a common code.
+ */
+void AssetsAndroid::init()
+{
+#ifdef ANDROID
+ if (m_file_manager == NULL)
+ return;
+
+ bool needs_extract_data = false;
+ const std::string version = std::string("supertuxkart.") + STK_VERSION;
+
+ // Add some paths to check
+ std::vector paths;
+
+ if (getenv("SUPERTUXKART_DATADIR"))
+ paths.push_back(getenv("SUPERTUXKART_DATADIR"));
+
+ if (getenv("EXTERNAL_STORAGE"))
+ paths.push_back(getenv("EXTERNAL_STORAGE"));
+
+ if (getenv("SECONDARY_STORAGE"))
+ paths.push_back(getenv("SECONDARY_STORAGE"));
+
+ if (global_android_app->activity->externalDataPath)
+ paths.push_back(global_android_app->activity->externalDataPath);
+
+ if (global_android_app->activity->internalDataPath)
+ paths.push_back(global_android_app->activity->internalDataPath);
+
+ paths.push_back("/sdcard/");
+ paths.push_back("/storage/sdcard0/");
+ paths.push_back("/storage/sdcard1/");
+ paths.push_back("/data/data/org.supertuxkart.stk/files/");
+
+ // Check if STK data is available somewhere
+ for (std::string path : paths)
+ {
+ Log::info("AssetsAndroid", "Check data files in: %s", path.c_str());
+
+ if (m_file_manager->fileExists(path + "/stk/data/" + version))
+ {
+ Log::info("AssetsAndroid", "Data files found in: %s", path.c_str());
+ m_stk_dir = path + "/stk";
+ break;
+ }
+
+ if (m_file_manager->fileExists(path + "/supertuxkart/data/" + version))
+ {
+ Log::info("AssetsAndroid", "Data files found in: %s", path.c_str());
+ m_stk_dir = path + "/supertuxkart";
+ break;
+ }
+ }
+
+ // Create data dir if it's not available anywhere
+ if (m_stk_dir.size() == 0)
+ {
+ for (std::string path : paths)
+ {
+ if (m_file_manager->checkAndCreateDirectoryP(path + "/stk/data"))
+ {
+ Log::info("AssetsAndroid", "Data directory created in: %s",
+ path.c_str());
+ m_stk_dir = path + "/stk";
+ needs_extract_data = true;
+ break;
+ }
+ }
+ }
+
+ // We can't continue if STK dir has not been found
+ if (m_stk_dir.size() == 0)
+ {
+ Log::fatal("AssetsAndroid", "Fatal error: Couldn't find Supertuxkart "
+ "data directory");
+ }
+
+ // Check if assets were extracted properly
+ if (!m_file_manager->fileExists(m_stk_dir + "/.extracted") &&
+ !needs_extract_data)
+ {
+ needs_extract_data = true;
+ Log::warn("AssetsAndroid", "Assets seem to be not extracted properly, "
+ "because the .extracted file doesn't exist. Force "
+ "extracting assets...");
+ }
+
+ if (!m_file_manager->checkAndCreateDirectoryP(m_stk_dir + "/home"))
+ {
+ Log::warn("AssetsAndroid", "Couldn't create home directory");
+ }
+
+ // Set some useful variables
+ setenv("SUPERTUXKART_DATADIR", m_stk_dir.c_str(), 1);
+ setenv("HOME", (m_stk_dir + "/home").c_str(), 1);
+ setenv("XDG_CONFIG_HOME", (m_stk_dir + "/home").c_str(), 1);
+
+ // Extract data directory from apk if it's needed
+ if (needs_extract_data)
+ {
+ removeData();
+ extractData();
+
+ if (!m_file_manager->fileExists(m_stk_dir + "/.extracted"))
+ {
+ Log::fatal("AssetsAndroid", "Fatal error: Assets were not "
+ "extracted properly");
+ }
+ }
+
+#endif
+}
+
+//-----------------------------------------------------------------------------
+/** A function that extracts whole data directory from apk file to a real
+ * path in the filesystem
+ */
+void AssetsAndroid::extractData()
+{
+#ifdef ANDROID
+ const std::string dirs_list = "directories.txt";
+
+ bool success = true;
+
+ // Create .nomedia file
+ touchFile(m_stk_dir + "/.nomedia");
+
+ // Extract base directory first, so that we will be able to open the file
+ // with dir names
+ success = extractDir("");
+
+ if (!success)
+ {
+ Log::error("AssetsAndroid", "Error: Couldn't extract main directory.");
+ return;
+ }
+
+ std::fstream file(m_stk_dir + "/" + dirs_list, std::ios::in);
+
+ if (file.good())
+ {
+ unsigned int lines_count = 0;
+
+ while (!file.eof())
+ {
+ std::string dir_name;
+ getline(file, dir_name);
+
+ if (dir_name.length() == 0 || dir_name.at(0) == '#')
+ continue;
+
+ lines_count++;
+ }
+
+ if (lines_count > 0)
+ {
+ file.clear();
+ file.seekg(0, std::ios::beg);
+
+ ProgressBarAndroid* progress_bar = new ProgressBarAndroid();
+ progress_bar->draw(0.0f);
+ unsigned int current_line = 1;
+
+ while (!file.eof())
+ {
+ std::string dir_name;
+ getline(file, dir_name);
+
+ if (dir_name.length() == 0 || dir_name.at(0) == '#')
+ continue;
+
+ success = extractDir(dir_name);
+
+ assert(lines_count > 0);
+ progress_bar->draw((float)(current_line) / lines_count);
+ current_line++;
+
+ if (progress_bar->closeEventReceived())
+ {
+ success = false;
+ }
+
+ if (!success)
+ break;
+ }
+
+ delete progress_bar;
+ }
+ }
+ else
+ {
+ Log::warn("AssetsAndroid", "Warning: Cannot open %s file. Ignoring "
+ "extraction of other directories.", dirs_list.c_str());
+ }
+
+ file.close();
+
+ // Mark the extraction as successful if everything is ok
+ if (success)
+ {
+ touchFile(m_stk_dir + "/.extracted");
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+/** A function that extracts selected directory from apk file
+ * \param dir_name Directory to extract from assets
+ * \return True if successfully extracted
+ */
+bool AssetsAndroid::extractDir(std::string dir_name)
+{
+#ifdef ANDROID
+ AAssetManager* amgr = global_android_app->activity->assetManager;
+
+ Log::info("AssetsAndroid", "Extracting %s directory",
+ dir_name.length() > 0 ? dir_name.c_str() : "main");
+
+ std::string output_dir = dir_name;
+
+ if (m_stk_dir.length() > 0)
+ {
+ output_dir = m_stk_dir + "/" + dir_name;
+ }
+
+ AAssetDir* asset_dir = AAssetManager_openDir(amgr, dir_name.c_str());
+
+ if (asset_dir == NULL)
+ {
+ Log::warn("AssetsAndroid", "Couldn't get asset dir: %s",
+ dir_name.c_str());
+ return true;
+ }
+
+ if (!m_file_manager->checkAndCreateDirectoryP(output_dir))
+ {
+ Log::warn("AssetsAndroid", "Couldn't create %s directory",
+ output_dir.c_str());
+ return false;
+ }
+
+ const int buf_size = 65536;
+ char* buf = new char[buf_size]();
+ bool extraction_failed = false;
+
+ while (!extraction_failed)
+ {
+ const char* filename = AAssetDir_getNextFileName(asset_dir);
+
+ // Check if finished
+ if (filename == NULL)
+ break;
+
+ if (strlen(filename) == 0)
+ continue;
+
+ std::string file_path = std::string(filename);
+
+ if (dir_name.length() > 0)
+ {
+ file_path = dir_name + "/" + std::string(filename);
+ }
+
+ AAsset* asset = AAssetManager_open(amgr, file_path.c_str(),
+ AASSET_MODE_STREAMING);
+
+ if (asset == NULL)
+ {
+ Log::warn("AssetsAndroid", "Asset is null: %s", filename);
+ continue;
+ }
+
+ std::string output_path = output_dir + "/" + std::string(filename);
+ std::fstream out_file(output_path, std::ios::out | std::ios::binary);
+
+ if (!out_file.good())
+ {
+ extraction_failed = true;
+ Log::error("AssetsAndroid", "Couldn't create a file: %s", filename);
+ AAsset_close(asset);
+ break;
+ }
+
+ int nb_read = 0;
+ while ((nb_read = AAsset_read(asset, buf, buf_size)) > 0)
+ {
+ out_file.write(buf, nb_read);
+
+ if (out_file.fail())
+ {
+ extraction_failed = true;
+ break;
+ }
+ }
+
+ out_file.close();
+
+ if (out_file.fail() || extraction_failed)
+ {
+ extraction_failed = true;
+ Log::error("AssetsAndroid", "Extraction failed for file: %s",
+ filename);
+ }
+
+ AAsset_close(asset);
+ }
+
+ delete[] buf;
+
+ AAssetDir_close(asset_dir);
+
+ return !extraction_failed;
+#endif
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+/** A function that removes whole STK data directory
+ */
+void AssetsAndroid::removeData()
+{
+#ifdef ANDROID
+ if (m_stk_dir.length() == 0)
+ return;
+
+ // Make sure that we are not accidentally removing wrong directory
+ if (m_stk_dir.find("/stk") == std::string::npos &&
+ m_stk_dir.find("/supertuxkart") == std::string::npos)
+ {
+ Log::error("AssetsAndroid", "Invalid data directory: %s",
+ m_stk_dir.c_str());
+ assert(false);
+ return;
+ }
+
+ std::set files;
+ m_file_manager->listFiles(files, m_stk_dir, true);
+
+ for (std::string file : files)
+ {
+ if (file == m_stk_dir + "/." || file == m_stk_dir + "/..")
+ continue;
+
+ // Don't delete home directory that contains configuration files
+ // and add-ons
+ if (file == m_stk_dir + "/home")
+ continue;
+
+ // Don't delete .nomedia file. It has a sense to keep it for home
+ // directory, i.e. for textures of add-on karts etc.
+ if (file == m_stk_dir + "/.nomedia")
+ continue;
+
+ Log::info("AssetsAndroid", "Deleting file: %s\n", file.c_str());
+
+ if (m_file_manager->isDirectory(file))
+ {
+ m_file_manager->removeDirectory(file);
+ }
+ else
+ {
+ m_file_manager->removeFile(file);
+ }
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+/** A function that creates empty file
+ * \param path A path to the file that should be created
+ */
+void AssetsAndroid::touchFile(std::string path)
+{
+#ifdef ANDROID
+ if (m_file_manager->fileExists(path))
+ return;
+
+ std::fstream file(path, std::ios::out | std::ios::binary);
+
+ if (!file.good())
+ {
+ Log::warn("AssetsAndroid", "Error: Cannot create %s file.",
+ path.c_str());
+ }
+
+ file.close();
+#endif
+}
+
+//-----------------------------------------------------------------------------
diff --git a/src/io/assets_android.hpp b/src/io/assets_android.hpp
new file mode 100644
index 000000000..0cccf566a
--- /dev/null
+++ b/src/io/assets_android.hpp
@@ -0,0 +1,44 @@
+// SuperTuxKart - a fun racing game with go-kart
+// Copyright (C) 2014-2015 SuperTuxKart-Team
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 3
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+#ifndef HEADER_ASSETS_ANDROID_HPP
+#define HEADER_ASSETS_ANDROID_HPP
+
+#include
+
+class FileManager;
+
+class AssetsAndroid
+{
+private:
+ FileManager* m_file_manager;
+ std::string m_stk_dir;
+
+ void extractData();
+ bool extractDir(std::string dir_name);
+ void removeData();
+ void touchFile(std::string path);
+
+public:
+ AssetsAndroid(FileManager* file_manager);
+ ~AssetsAndroid() {};
+
+ void init();
+};
+
+
+#endif
diff --git a/src/main_android.cpp b/src/main_android.cpp
new file mode 100644
index 000000000..ec3505584
--- /dev/null
+++ b/src/main_android.cpp
@@ -0,0 +1,62 @@
+#ifdef ANDROID
+
+#include "config/user_config.hpp"
+#include "graphics/irr_driver.hpp"
+#include "utils/log.hpp"
+
+
+extern int main(int argc, char *argv[]);
+
+void override_default_params()
+{
+ // It has an effect only on the first run, when config file is created.
+ // So that we can still modify these params in STK options and user's
+ // choice will be then remembered.
+
+ // Set smaller texture size to avoid high RAM usage
+ UserConfigParams::m_max_texture_size = 256;
+ UserConfigParams::m_high_definition_textures = false;
+
+ // Disable advanced lighting by default to make the game playable
+ UserConfigParams::m_dynamic_lights = false;
+
+ // Enable touch steering and screen keyboard
+ UserConfigParams::m_multitouch_enabled = true;
+ UserConfigParams::m_screen_keyboard = true;
+
+ // It shouldn't matter, but STK is always run in fullscreen on android
+ UserConfigParams::m_fullscreen = true;
+
+ // Make sure that user can play every track even if there are installed
+ // only few tracks and it's impossible to finish overworld challenges
+ UserConfigParams::m_everything_unlocked = true;
+
+ // Create default user istead of showing login screen to make life easier
+ UserConfigParams::m_enforce_current_player = true;
+
+ // Just for debugging
+ UserConfigParams::m_log_errors_to_console = true;
+}
+
+void android_main(struct android_app* app)
+{
+ Log::info("AndroidMain", "Loading application...");
+
+ app_dummy();
+
+ override_default_params();
+
+ global_android_app = app;
+ main(0, {});
+
+ Log::info("AndroidMain", "Closing STK...");
+
+ // TODO: Irrlicht device is properly waiting for destroy event, but
+ // some global variables are not initialized/cleared in functions and thus
+ // its state is remembered when the window is restored. We will use exit
+ // call to make sure that all variables are cleared until a proper fix will
+ // be done.
+ exit(0);
+}
+
+#endif
diff --git a/src/utils/progress_bar_android.cpp b/src/utils/progress_bar_android.cpp
new file mode 100644
index 000000000..1636c6e37
--- /dev/null
+++ b/src/utils/progress_bar_android.cpp
@@ -0,0 +1,205 @@
+// SuperTuxKart - a fun racing game with go-kart
+// Copyright (C) 2014-2015 SuperTuxKart-Team
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 3
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+
+#include "config/user_config.hpp"
+#include "graphics/irr_driver.hpp"
+#include "utils/log.hpp"
+#include "utils/progress_bar_android.hpp"
+
+#ifdef ANDROID
+
+ProgressBarAndroid::ProgressBarAndroid()
+{
+ m_program = 0;
+ m_vertex_shader = 0;
+ m_fragment_shader = 0;
+ m_position = 0;
+ m_progress = 0;
+ m_vbo = 0;
+ m_device = NULL;
+ m_initialized = false;
+ m_close_event_received = false;
+
+ init();
+}
+
+ProgressBarAndroid::~ProgressBarAndroid()
+{
+ close();
+}
+
+bool ProgressBarAndroid::compileShaders()
+{
+ const GLchar* vsh =
+ "precision mediump float;"
+ "attribute vec2 position;"
+ "uniform float progress;"
+ "void main(void)"
+ "{"
+ " float pos_x = (position.x + 1.0) * progress - 1.0;"
+ " float pos_y = position.y * 0.1 - 0.6;"
+ " gl_Position = vec4(pos_x, pos_y, 0.0, 1.0);"
+ "}";
+
+ const GLchar* fsh =
+ "precision mediump float;"
+ "void main(void)"
+ "{"
+ " gl_FragColor = vec4(0.5, 0.5, 0.5, 1.0);"
+ "}";
+
+ m_vertex_shader = glCreateShader(GL_VERTEX_SHADER);
+ glShaderSource(m_vertex_shader, 1, &vsh, NULL);
+ glCompileShader(m_vertex_shader);
+
+ GLint success;
+ glGetShaderiv(m_vertex_shader, GL_COMPILE_STATUS, &success);
+ if (!success)
+ {
+ Log::error("ProgressBarAndroid", "Failed to compile vertex shader.");
+ return false;
+ }
+
+ m_fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
+ glShaderSource(m_fragment_shader, 1, &fsh, NULL);
+ glCompileShader(m_fragment_shader);
+
+ glGetShaderiv(m_fragment_shader, GL_COMPILE_STATUS, &success);
+ if (!success)
+ {
+ Log::error("ProgressBarAndroid", "Failed to compile fragment shader.");
+ return false;
+ }
+
+ m_program = glCreateProgram();
+
+ glAttachShader(m_program, m_vertex_shader);
+ glAttachShader(m_program, m_fragment_shader);
+ glLinkProgram(m_program);
+
+ glGetProgramiv(m_program, GL_LINK_STATUS, &success);
+ if (!success)
+ {
+ Log::error("ProgressBarAndroid", "Failed to link program.");
+ return false;
+ }
+
+ m_position = glGetAttribLocation(m_program, "position");
+ if (m_position == -1)
+ {
+ Log::error("ProgressBarAndroid", "Failed to get attrib location.");
+ return false;
+ }
+
+ m_progress = glGetUniformLocation(m_program, "progress");
+ if (m_progress == -1)
+ {
+ Log::error("ProgressBarAndroid", "Failed to get uniform location.");
+ return false;
+ }
+
+ return true;
+}
+
+void ProgressBarAndroid::deleteShaders()
+{
+ glDeleteShader(m_vertex_shader);
+ glDeleteShader(m_fragment_shader);
+ glDeleteProgram(m_program);
+}
+
+void ProgressBarAndroid::init()
+{
+ SIrrlichtCreationParameters params;
+ params.DriverType = video::EDT_OGLES2;
+ params.Bits = 32;
+ params.Fullscreen = UserConfigParams::m_fullscreen;
+ params.WindowSize = core::dimension2du(640, 480);
+#if defined(ANDROID)
+ params.PrivateData = (void*)global_android_app;
+#endif
+
+ m_device = createDeviceEx(params);
+
+ if (!m_device)
+ return;
+
+ bool success = compileShaders();
+
+ if (!success)
+ return;
+
+ const GLfloat vertices[] =
+ {
+ -1, 1, 1, -1, -1, -1,
+ 1, -1, -1, 1, 1, 1
+ };
+
+ glGenBuffers(1, &m_vbo);
+ glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
+
+ glUseProgram(m_program);
+ glEnableVertexAttribArray(m_position);
+ glVertexAttribPointer(m_position, 2, GL_FLOAT, GL_FALSE, 0, 0);
+
+ glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ m_initialized = true;
+}
+
+void ProgressBarAndroid::close()
+{
+ glDisableVertexAttribArray(m_position);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glUseProgram(0);
+
+ deleteShaders();
+
+ if (m_device != NULL)
+ {
+ m_device->closeDevice();
+ m_device->clearSystemMessages();
+ m_device->run();
+ m_device->drop();
+ m_device = NULL;
+ }
+
+ m_initialized = false;
+}
+
+void ProgressBarAndroid::draw(float value)
+{
+ if (!m_initialized || m_close_event_received)
+ return;
+
+ value = value > 1.0f ? 1.0f : value;
+
+ m_close_event_received = !m_device->run();
+
+ m_device->getVideoDriver()->beginScene(true, true);
+
+ glClear(GL_COLOR_BUFFER_BIT);
+ glUniform1f(m_progress, value);
+ glDrawArrays(GL_TRIANGLES, 0, 6);
+
+ m_device->getVideoDriver()->endScene();
+}
+
+#endif
diff --git a/src/utils/progress_bar_android.hpp b/src/utils/progress_bar_android.hpp
new file mode 100644
index 000000000..b712457c4
--- /dev/null
+++ b/src/utils/progress_bar_android.hpp
@@ -0,0 +1,55 @@
+// SuperTuxKart - a fun racing game with go-kart
+// Copyright (C) 2014-2015 SuperTuxKart-Team
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 3
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+#ifndef HEADER_PROGRESS_BAR_ANDROID_HPP
+#define HEADER_PROGRESS_BAR_ANDROID_HPP
+
+#ifdef ANDROID
+
+#include "IrrlichtDevice.h"
+#include "graphics/gl_headers.hpp"
+
+class ProgressBarAndroid
+{
+private:
+ GLuint m_program;
+ GLuint m_vertex_shader;
+ GLuint m_fragment_shader;
+ GLint m_position;
+ GLint m_progress;
+ GLuint m_vbo;
+
+ irr::IrrlichtDevice* m_device;
+ bool m_initialized;
+ bool m_close_event_received;
+
+ bool compileShaders();
+ void deleteShaders();
+ void init();
+ void close();
+
+public:
+ ProgressBarAndroid();
+ ~ProgressBarAndroid();
+
+ void draw(float value);
+ bool closeEventReceived() {return m_close_event_received;}
+};
+
+#endif
+
+#endif