├── settings.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── app
├── src
│ └── main
│ │ ├── res
│ │ └── values
│ │ │ └── strings.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── org
│ │ └── libsdl
│ │ └── app
│ │ └── SDLActivity.java
└── build.gradle
├── .gitignore
├── UNLICENSE
├── main
├── build.gradle
└── src
│ └── main.cpp
├── README.md
├── gradlew.bat
├── gradlew
└── SDL2
└── build.gradle
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 | include ':SDL2'
3 | include ':main'
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stephen47/android-sdl2-gradle-template/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | SDL Example
4 |
5 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Apr 10 15:27:10 PDT 2013
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-bin.zip
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | /build/
3 |
4 | # Ignore Gradle GUI config
5 | gradle-app.setting
6 |
7 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
8 | !gradle-wrapper.jar
9 |
10 | # Cache of project
11 | .gradletasknamecache
12 |
13 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
14 | # gradle/wrapper/gradle-wrapper.properties
15 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/UNLICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/main/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.model.native'
2 |
3 | def lib_distribution_root = '../distribution'
4 | model {
5 | repositories {
6 | libs(PrebuiltLibraries) {
7 | SDL2 {
8 | headers.srcDir "../SDL2/include"
9 | binaries.withType(SharedLibraryBinary) {
10 | sharedLibraryFile = file("${lib_distribution_root}/SDL2/lib/${targetPlatform.getName()}/libSDL2.so")
11 | }
12 | }
13 | }
14 | }
15 |
16 | android {
17 | compileSdkVersion = 24
18 | buildToolsVersion = '23.0.3'
19 |
20 | defaultConfig {
21 | minSdkVersion.apiLevel = 13
22 | targetSdkVersion.apiLevel = 24
23 | versionCode = 1
24 | versionName = '1.0'
25 | }
26 | ndk {
27 | moduleName = 'main'
28 | cppFlags.addAll(["-I" + file("../SDL2/include/").absolutePath])
29 | CFlags.addAll(["-I" + file("../SDL2/include/").absolutePath])
30 | stl "stlport_static"
31 | }
32 |
33 | sources {
34 | main {
35 | jni {
36 | dependencies {
37 | library 'SDL2' linkage 'shared'
38 | }
39 | source {
40 | srcDirs 'src/'
41 | srcDirs '../SDL2/src/main/android/'
42 | }
43 | }
44 | }
45 | }
46 |
47 | }
48 | }
49 |
50 | // This is just copy out the header file and built lib into distribution
51 | // directory for clint application to use; it is a small overhead of this sample:
52 | // both lib and app are put inside one project space [save maintenance time]
53 | task(distributeLib, type : Copy) {
54 | // trigger build library
55 | dependsOn assemble
56 | into '../distribution/main/'
57 | from('build/outputs/native/release/lib') {
58 | into 'lib/'
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/main/src/main.cpp:
--------------------------------------------------------------------------------
1 | #include "SDL.h"
2 | #include
3 |
4 | int main(int argc, char* argv[]) {
5 |
6 | SDL_Window *window; // Declare a pointer
7 |
8 | SDL_Init(SDL_INIT_VIDEO); // Initialize SDL2
9 |
10 | // Create an application window with the following settings:
11 | window = SDL_CreateWindow(
12 | "An SDL2 window", // window title
13 | SDL_WINDOWPOS_UNDEFINED, // initial x position
14 | SDL_WINDOWPOS_UNDEFINED, // initial y position
15 | 640, // width, in pixels
16 | 480, // height, in pixels
17 | SDL_WINDOW_OPENGL // flags - see below
18 | );
19 |
20 | // Check that the window was successfully created
21 | if (window == NULL) {
22 | // In the case that the window could not be made...
23 | printf("Could not create window: %s\n", SDL_GetError());
24 | return 1;
25 | }
26 |
27 | // The window is open: could enter program loop here (see SDL_PollEvent())
28 | // Setup renderer
29 | SDL_Renderer* renderer = NULL;
30 | renderer = SDL_CreateRenderer( window, -1, SDL_RENDERER_ACCELERATED);
31 |
32 | // Set render color to red ( background will be rendered in this color )
33 | SDL_SetRenderDrawColor( renderer, 255, 0, 0, 255 );
34 |
35 | // Clear winow
36 | SDL_RenderClear( renderer );
37 |
38 | // Creat a rect at pos ( 50, 50 ) that's 50 pixels wide and 50 pixels high.
39 | SDL_Rect r;
40 | r.x = 50;
41 | r.y = 50;
42 | r.w = 50;
43 | r.h = 50;
44 |
45 | // Set render color to blue ( rect will be rendered in this color )
46 | SDL_SetRenderDrawColor( renderer, 0, 0, 255, 255 );
47 |
48 | // Render rect
49 | SDL_RenderFillRect( renderer, &r );
50 |
51 | // Render the rect to the screen
52 | SDL_RenderPresent(renderer);
53 |
54 | SDL_Delay(8000); // Pause execution for 3000 milliseconds, for example
55 |
56 | // Close and destroy the window
57 | SDL_DestroyWindow(window);
58 |
59 | // Clean up
60 | SDL_Quit();
61 | return 0;
62 | }
63 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # This repository is no longer maintained.
2 |
3 | This is being left up for reference but be warned it no longer builds on the modern Android toolchain.
4 |
5 | # android-sdl2-gradle-template
6 |
7 | ## Synopsis
8 |
9 | This is an example project for using libSDL2 (https://www.libsdl.org/) in an Android Gradle project. This project came about as I couldn't find a good example project using libSDL2 in a gradle project. This project will create 2 shared NDK libraries, libSDL2 and libmain, the latter containing a very basic SDL2 program which displays a blue square on a red screen.
10 |
11 | ## Requirements
12 | - JDK and JRE 8 (I have been using Debian 8 using jessie-backports to get openjdk-8-jre and openjdk-8-jdk)
13 | - Android SDK and NDK (with Android Build-tools 24.0.2 and Android Platform API 23, though these are configurable)
14 | - ANDROID_HOME and ANDROID_NDK_HOME environment variables set (I did this in /etc/environment)
15 |
16 | ## Before you begin
17 | I have not tested this in Android studio, I have just tested this using the Gradle wrapper command line. It is using gradle-experimental plugin version 0.8.0-beta3 and gradle version 2.14.1.
18 |
19 | ## Instructions
20 |
21 | Download libSDL2 from the website (https://www.libsdl.org/). Copy the src and include directories from the libSDL2 source into SDL2/ so you end up with the following directories:
22 |
23 | ```
24 | SDL2/src
25 | SDL2/include
26 | ```
27 |
28 | We also need to copy the example Java file from the libSDL2 source code into our project at the following location (copy from SDL2-2.0.4/android-project/src/org/libsdl/app/SDLActivity.java):
29 |
30 | ```
31 | app/src/main/java/org/libsdl/app/SDLActivity.java
32 | ```
33 |
34 | ## Compiling the app
35 |
36 | ```
37 | ./gradlew :SDL2:distributeLib
38 | ```
39 | This will compile libSDL2 as a shared library.
40 |
41 | ```
42 | ./gradlew :main:distributeLib
43 | ```
44 | This will compile our little project (source code in main/src/) as a shared library. You can now skip this step and just run assembleDebug as below. What I would suggest is you compile SDL2 first, commit it to source control, then just build the project as normal (assembleDebug).
45 |
46 | ```
47 | ./gradlew assembleDebug
48 | ```
49 | This will build our apk's and output them to app/build/outputs/apk/.
50 |
51 | ## Thanks
52 |
53 | The example libSDL2 program which draws the square on screen was found at https://stackoverflow.com/questions/21890627/drawing-a-rectangle-with-sdl2/21903973#21903973.
54 |
55 | This guide was incredibly useful while I tried to figure out how to get this working: http://lazyfoo.net/tutorials/SDL/52_hello_mobile/android_linux/index.php
56 |
57 | The Google NDK example projects were very helpful: https://github.com/googlesamples/android-ndk
58 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.model.application'
2 |
3 | def lib_distribution_root = '../distribution'
4 |
5 | gradle.projectsEvaluated {
6 | tasks.withType(JavaCompile) {
7 | options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
8 | }
9 | }
10 |
11 | model {
12 | repositories {
13 | libs(PrebuiltLibraries) {
14 | SDL2 {
15 | headers.srcDir "../SDL2/include"
16 | binaries.withType(SharedLibraryBinary) {
17 | sharedLibraryFile = file("${lib_distribution_root}/SDL2/lib/${targetPlatform.getName()}/libSDL2.so")
18 | }
19 | }
20 | main {
21 | binaries.withType(SharedLibraryBinary) {
22 | sharedLibraryFile = file("${lib_distribution_root}/main/lib/${targetPlatform.getName()}/libmain.so")
23 | }
24 | }
25 | }
26 | }
27 |
28 | android {
29 | compileSdkVersion = 23
30 | buildToolsVersion = '24.0.2'
31 |
32 | defaultConfig {
33 | applicationId = 'org.libsdl.app'
34 | minSdkVersion.apiLevel = 4
35 | targetSdkVersion.apiLevel = 23
36 | }
37 | sources {
38 | main {
39 | jni {
40 | dependencies {
41 | //library 'SDL2' linkage 'shared'
42 | project ':main' linkage 'shared'
43 | }
44 | }
45 | jniLibs {
46 | source {
47 | //srcDir "${lib_distribution_root}/SDL2/lib"
48 | srcDir "${lib_distribution_root}/main/lib"
49 | }
50 | }
51 | }
52 | }
53 |
54 | buildTypes {
55 | release {
56 | minifyEnabled = false
57 | proguardFiles.add(file('proguard-rules.txt'))
58 | }
59 | }
60 | productFlavors {
61 | // for detailed abiFilter descriptions, refer to "Supported ABIs" @
62 | // https://developer.android.com/ndk/guides/abis.html#sa
63 | create("arm") {
64 | ndk.abiFilters.add("armeabi")
65 | }
66 | create("arm7") {
67 | ndk.abiFilters.add("armeabi-v7a")
68 | }
69 | create("arm8") {
70 | ndk.abiFilters.add("arm64-v8a")
71 | }
72 | create("x86") {
73 | ndk.abiFilters.add("x86")
74 | }
75 | create("x86-64") {
76 | ndk.abiFilters.add("x86_64")
77 | }
78 | create("mips") {
79 | ndk.abiFilters.add("mips")
80 | }
81 | create("mips-64") {
82 | ndk.abiFilters.add("mips64")
83 | }
84 | // To include all cpu architectures, leaves abiFilters empty
85 | create("all")
86 | }
87 | }
88 | }
89 |
90 | tasks.whenTaskAdded { task ->
91 | if (task.name.contains('compile')) {
92 | task.dependsOn ':main:distributeLib'
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/SDL2/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.model.native'
2 |
3 | model {
4 | android {
5 | compileSdkVersion = 24
6 | buildToolsVersion = '23.0.3'
7 |
8 | defaultConfig {
9 | minSdkVersion.apiLevel = 13
10 | targetSdkVersion.apiLevel = 24
11 | versionCode = 1
12 | versionName = '1.0'
13 | }
14 | ndk {
15 | moduleName = 'SDL2'
16 | ldLibs.addAll(["GLESv1_CM", "EGL", "GLESv2", "log", "android", "dl"])
17 | CFlags.addAll(["-DGL_GLEXT_PROTOTYPES"])
18 | CFlags.addAll(["-I" + file("include/").absolutePath,"-DGL_GLEXT_PROTOTYPES"])
19 | }
20 |
21 | sources {
22 | main {
23 | jni {
24 | source {
25 | srcDir "src"
26 | exclude "audio/alsa/"
27 | exclude "audio/arts/"
28 | exclude "audio/bsd/"
29 | exclude "audio/coreaudio/"
30 | exclude "audio/directsound/"
31 | exclude "audio/disk/"
32 | exclude "audio/dsp/"
33 | exclude "audio/emscripten/"
34 | exclude "audio/esd/"
35 | exclude "audio/fusionsound/"
36 | exclude "audio/haiku/"
37 | exclude "audio/nacl/"
38 | exclude "audio/nas/"
39 | exclude "audio/psp/"
40 | exclude "audio/pulseaudio/"
41 | exclude "audio/qsa/"
42 | exclude "audio/sndio/"
43 | exclude "audio/sun/"
44 | exclude "audio/winmm/"
45 | exclude "audio/xaudio2/"
46 | exclude "core/linux/"
47 | exclude "core/windows/"
48 | exclude "core/winrt/"
49 | exclude "file/cocoa/"
50 | exclude "haptic/windows/"
51 | exclude "haptic/darwin/"
52 | exclude "haptic/linux/"
53 | exclude "libm/"
54 | exclude "main/dummy/"
55 | exclude "main/android/"
56 | exclude "main/haiku/"
57 | exclude "main/nacl/"
58 | exclude "main/psp/"
59 | exclude "main/windows/"
60 | exclude "main/winrt/"
61 | exclude "joystick/bsd/"
62 | exclude "joystick/darwin/"
63 | exclude "joystick/dummy/"
64 | exclude "joystick/emscripten/"
65 | exclude "joystick/haiku/"
66 | exclude "joystick/iphoneos/"
67 | exclude "joystick/linux/"
68 | exclude "joystick/psp/"
69 | exclude "joystick/windows/"
70 | exclude "loadso/dummy/"
71 | exclude "loadso/haiku/"
72 | exclude "loadso/windows/"
73 | exclude "power/emscripten/"
74 | exclude "power/haiku/"
75 | exclude "power/linux/"
76 | exclude "power/macosx/"
77 | exclude "power/psp/"
78 | exclude "power/uikit/"
79 | exclude "power/windows/"
80 | exclude "power/winrt/"
81 | exclude "filesystem/cocoa/"
82 | exclude "filesystem/dummy/"
83 | exclude "filesystem/emscripten/"
84 | exclude "filesystem/haiku/"
85 | exclude "filesystem/nacl/"
86 | exclude "filesystem/unix/"
87 | exclude "filesystem/windows/"
88 | exclude "filesystem/winrt/"
89 | exclude "render/direct3d/"
90 | exclude "render/direct3d11/"
91 | exclude "thread/generic/"
92 | exclude "thread/psp/"
93 | exclude "thread/stdcpp/"
94 | exclude "thread/windows/"
95 | exclude "timer/dummy/"
96 | exclude "timer/haiku/"
97 | exclude "timer/psp/"
98 | exclude "timer/windows/"
99 | exclude "video/cocoa/"
100 | exclude "video/directfb/"
101 | exclude "video/dummy/"
102 | exclude "video/emscripten/"
103 | exclude "video/haiku/"
104 | exclude "video/mir/"
105 | exclude "video/nacl/"
106 | exclude "video/pandora/"
107 | exclude "video/psp/"
108 | exclude "video/raspberry/"
109 | exclude "video/uikit/"
110 | exclude "video/vivante/"
111 | exclude "video/wayland/"
112 | exclude "video/windows/"
113 | exclude "video/winrt/"
114 | exclude "video/x11/"
115 | exclude "haptic/windows/"
116 | exclude "haptic/windows/"
117 | exclude "haptic/windows/"
118 | exclude "haptic/windows/"
119 | exclude "haptic/windows/"
120 | exclude "haptic/windows/"
121 | exclude "haptic/windows/"
122 | exclude "haptic/windows/"
123 | exclude "haptic/windows/"
124 | exclude "haptic/windows/"
125 | }
126 | }
127 | }
128 | }
129 |
130 | }
131 | }
132 |
133 | // This is just copy out the header file and built lib into distribution
134 | // directory for clint application to use; it is a small overhead of this sample:
135 | // both lib and app are put inside one project space [save maintenance time]
136 | task(distributeLib, type : Copy) {
137 | // trigger build library
138 | dependsOn assemble
139 | into '../distribution/SDL2/'
140 | from('build/outputs/native/release/lib') {
141 | into 'lib/'
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/app/src/main/java/org/libsdl/app/SDLActivity.java:
--------------------------------------------------------------------------------
1 | package org.libsdl.app;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 | import java.util.ArrayList;
6 | import java.util.Arrays;
7 | import java.util.Collections;
8 | import java.util.Comparator;
9 | import java.util.List;
10 | import java.lang.reflect.Method;
11 |
12 | import android.app.*;
13 | import android.content.*;
14 | import android.text.InputType;
15 | import android.view.*;
16 | import android.view.inputmethod.BaseInputConnection;
17 | import android.view.inputmethod.EditorInfo;
18 | import android.view.inputmethod.InputConnection;
19 | import android.view.inputmethod.InputMethodManager;
20 | import android.widget.AbsoluteLayout;
21 | import android.widget.Button;
22 | import android.widget.LinearLayout;
23 | import android.widget.TextView;
24 | import android.os.*;
25 | import android.util.Log;
26 | import android.util.SparseArray;
27 | import android.graphics.*;
28 | import android.graphics.drawable.Drawable;
29 | import android.media.*;
30 | import android.hardware.*;
31 | import android.content.pm.ActivityInfo;
32 |
33 | /**
34 | SDL Activity
35 | */
36 | public class SDLActivity extends Activity {
37 | private static final String TAG = "SDL";
38 |
39 | // Keep track of the paused state
40 | public static boolean mIsPaused, mIsSurfaceReady, mHasFocus;
41 | public static boolean mExitCalledFromJava;
42 |
43 | /** If shared libraries (e.g. SDL or the native application) could not be loaded. */
44 | public static boolean mBrokenLibraries;
45 |
46 | // If we want to separate mouse and touch events.
47 | // This is only toggled in native code when a hint is set!
48 | public static boolean mSeparateMouseAndTouch;
49 |
50 | // Main components
51 | protected static SDLActivity mSingleton;
52 | protected static SDLSurface mSurface;
53 | protected static View mTextEdit;
54 | protected static ViewGroup mLayout;
55 | protected static SDLJoystickHandler mJoystickHandler;
56 |
57 | // This is what SDL runs in. It invokes SDL_main(), eventually
58 | protected static Thread mSDLThread;
59 |
60 | // Audio
61 | protected static AudioTrack mAudioTrack;
62 |
63 | /**
64 | * This method is called by SDL before loading the native shared libraries.
65 | * It can be overridden to provide names of shared libraries to be loaded.
66 | * The default implementation returns the defaults. It never returns null.
67 | * An array returned by a new implementation must at least contain "SDL2".
68 | * Also keep in mind that the order the libraries are loaded may matter.
69 | * @return names of shared libraries to be loaded (e.g. "SDL2", "main").
70 | */
71 | protected String[] getLibraries() {
72 | return new String[] {
73 | "SDL2",
74 | // "SDL2_image",
75 | // "SDL2_mixer",
76 | // "SDL2_net",
77 | // "SDL2_ttf",
78 | "main"
79 | };
80 | }
81 |
82 | // Load the .so
83 | public void loadLibraries() {
84 | for (String lib : getLibraries()) {
85 | System.loadLibrary(lib);
86 | }
87 | }
88 |
89 | /**
90 | * This method is called by SDL before starting the native application thread.
91 | * It can be overridden to provide the arguments after the application name.
92 | * The default implementation returns an empty array. It never returns null.
93 | * @return arguments for the native application.
94 | */
95 | protected String[] getArguments() {
96 | return new String[0];
97 | }
98 |
99 | public static void initialize() {
100 | // The static nature of the singleton and Android quirkyness force us to initialize everything here
101 | // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values
102 | mSingleton = null;
103 | mSurface = null;
104 | mTextEdit = null;
105 | mLayout = null;
106 | mJoystickHandler = null;
107 | mSDLThread = null;
108 | mAudioTrack = null;
109 | mExitCalledFromJava = false;
110 | mBrokenLibraries = false;
111 | mIsPaused = false;
112 | mIsSurfaceReady = false;
113 | mHasFocus = true;
114 | }
115 |
116 | // Setup
117 | @Override
118 | protected void onCreate(Bundle savedInstanceState) {
119 | Log.v(TAG, "Device: " + android.os.Build.DEVICE);
120 | Log.v(TAG, "Model: " + android.os.Build.MODEL);
121 | Log.v(TAG, "onCreate(): " + mSingleton);
122 | super.onCreate(savedInstanceState);
123 |
124 | SDLActivity.initialize();
125 | // So we can call stuff from static callbacks
126 | mSingleton = this;
127 |
128 | // Load shared libraries
129 | String errorMsgBrokenLib = "";
130 | try {
131 | loadLibraries();
132 | } catch(UnsatisfiedLinkError e) {
133 | System.err.println(e.getMessage());
134 | mBrokenLibraries = true;
135 | errorMsgBrokenLib = e.getMessage();
136 | } catch(Exception e) {
137 | System.err.println(e.getMessage());
138 | mBrokenLibraries = true;
139 | errorMsgBrokenLib = e.getMessage();
140 | }
141 |
142 | if (mBrokenLibraries)
143 | {
144 | AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this);
145 | dlgAlert.setMessage("An error occurred while trying to start the application. Please try again and/or reinstall."
146 | + System.getProperty("line.separator")
147 | + System.getProperty("line.separator")
148 | + "Error: " + errorMsgBrokenLib);
149 | dlgAlert.setTitle("SDL Error");
150 | dlgAlert.setPositiveButton("Exit",
151 | new DialogInterface.OnClickListener() {
152 | @Override
153 | public void onClick(DialogInterface dialog,int id) {
154 | // if this button is clicked, close current activity
155 | SDLActivity.mSingleton.finish();
156 | }
157 | });
158 | dlgAlert.setCancelable(false);
159 | dlgAlert.create().show();
160 |
161 | return;
162 | }
163 |
164 | // Set up the surface
165 | mSurface = new SDLSurface(getApplication());
166 |
167 | if(Build.VERSION.SDK_INT >= 12) {
168 | mJoystickHandler = new SDLJoystickHandler_API12();
169 | }
170 | else {
171 | mJoystickHandler = new SDLJoystickHandler();
172 | }
173 |
174 | mLayout = new AbsoluteLayout(this);
175 | mLayout.addView(mSurface);
176 |
177 | setContentView(mLayout);
178 |
179 | // Get filename from "Open with" of another application
180 | Intent intent = getIntent();
181 |
182 | if (intent != null && intent.getData() != null) {
183 | String filename = intent.getData().getPath();
184 | if (filename != null) {
185 | Log.v(TAG, "Got filename: " + filename);
186 | SDLActivity.onNativeDropFile(filename);
187 | }
188 | }
189 | }
190 |
191 | // Events
192 | @Override
193 | protected void onPause() {
194 | Log.v(TAG, "onPause()");
195 | super.onPause();
196 |
197 | if (SDLActivity.mBrokenLibraries) {
198 | return;
199 | }
200 |
201 | SDLActivity.handlePause();
202 | }
203 |
204 | @Override
205 | protected void onResume() {
206 | Log.v(TAG, "onResume()");
207 | super.onResume();
208 |
209 | if (SDLActivity.mBrokenLibraries) {
210 | return;
211 | }
212 |
213 | SDLActivity.handleResume();
214 | }
215 |
216 |
217 | @Override
218 | public void onWindowFocusChanged(boolean hasFocus) {
219 | super.onWindowFocusChanged(hasFocus);
220 | Log.v(TAG, "onWindowFocusChanged(): " + hasFocus);
221 |
222 | if (SDLActivity.mBrokenLibraries) {
223 | return;
224 | }
225 |
226 | SDLActivity.mHasFocus = hasFocus;
227 | if (hasFocus) {
228 | SDLActivity.handleResume();
229 | }
230 | }
231 |
232 | @Override
233 | public void onLowMemory() {
234 | Log.v(TAG, "onLowMemory()");
235 | super.onLowMemory();
236 |
237 | if (SDLActivity.mBrokenLibraries) {
238 | return;
239 | }
240 |
241 | SDLActivity.nativeLowMemory();
242 | }
243 |
244 | @Override
245 | protected void onDestroy() {
246 | Log.v(TAG, "onDestroy()");
247 |
248 | if (SDLActivity.mBrokenLibraries) {
249 | super.onDestroy();
250 | // Reset everything in case the user re opens the app
251 | SDLActivity.initialize();
252 | return;
253 | }
254 |
255 | // Send a quit message to the application
256 | SDLActivity.mExitCalledFromJava = true;
257 | SDLActivity.nativeQuit();
258 |
259 | // Now wait for the SDL thread to quit
260 | if (SDLActivity.mSDLThread != null) {
261 | try {
262 | SDLActivity.mSDLThread.join();
263 | } catch(Exception e) {
264 | Log.v(TAG, "Problem stopping thread: " + e);
265 | }
266 | SDLActivity.mSDLThread = null;
267 |
268 | //Log.v(TAG, "Finished waiting for SDL thread");
269 | }
270 |
271 | super.onDestroy();
272 | // Reset everything in case the user re opens the app
273 | SDLActivity.initialize();
274 | }
275 |
276 | @Override
277 | public boolean dispatchKeyEvent(KeyEvent event) {
278 |
279 | if (SDLActivity.mBrokenLibraries) {
280 | return false;
281 | }
282 |
283 | int keyCode = event.getKeyCode();
284 | // Ignore certain special keys so they're handled by Android
285 | if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ||
286 | keyCode == KeyEvent.KEYCODE_VOLUME_UP ||
287 | keyCode == KeyEvent.KEYCODE_CAMERA ||
288 | keyCode == 168 || /* API 11: KeyEvent.KEYCODE_ZOOM_IN */
289 | keyCode == 169 /* API 11: KeyEvent.KEYCODE_ZOOM_OUT */
290 | ) {
291 | return false;
292 | }
293 | return super.dispatchKeyEvent(event);
294 | }
295 |
296 | /** Called by onPause or surfaceDestroyed. Even if surfaceDestroyed
297 | * is the first to be called, mIsSurfaceReady should still be set
298 | * to 'true' during the call to onPause (in a usual scenario).
299 | */
300 | public static void handlePause() {
301 | if (!SDLActivity.mIsPaused && SDLActivity.mIsSurfaceReady) {
302 | SDLActivity.mIsPaused = true;
303 | SDLActivity.nativePause();
304 | mSurface.handlePause();
305 | }
306 | }
307 |
308 | /** Called by onResume or surfaceCreated. An actual resume should be done only when the surface is ready.
309 | * Note: Some Android variants may send multiple surfaceChanged events, so we don't need to resume
310 | * every time we get one of those events, only if it comes after surfaceDestroyed
311 | */
312 | public static void handleResume() {
313 | if (SDLActivity.mIsPaused && SDLActivity.mIsSurfaceReady && SDLActivity.mHasFocus) {
314 | SDLActivity.mIsPaused = false;
315 | SDLActivity.nativeResume();
316 | mSurface.handleResume();
317 | }
318 | }
319 |
320 | /* The native thread has finished */
321 | public static void handleNativeExit() {
322 | SDLActivity.mSDLThread = null;
323 | mSingleton.finish();
324 | }
325 |
326 |
327 | // Messages from the SDLMain thread
328 | static final int COMMAND_CHANGE_TITLE = 1;
329 | static final int COMMAND_UNUSED = 2;
330 | static final int COMMAND_TEXTEDIT_HIDE = 3;
331 | static final int COMMAND_SET_KEEP_SCREEN_ON = 5;
332 |
333 | protected static final int COMMAND_USER = 0x8000;
334 |
335 | /**
336 | * This method is called by SDL if SDL did not handle a message itself.
337 | * This happens if a received message contains an unsupported command.
338 | * Method can be overwritten to handle Messages in a different class.
339 | * @param command the command of the message.
340 | * @param param the parameter of the message. May be null.
341 | * @return if the message was handled in overridden method.
342 | */
343 | protected boolean onUnhandledMessage(int command, Object param) {
344 | return false;
345 | }
346 |
347 | /**
348 | * A Handler class for Messages from native SDL applications.
349 | * It uses current Activities as target (e.g. for the title).
350 | * static to prevent implicit references to enclosing object.
351 | */
352 | protected static class SDLCommandHandler extends Handler {
353 | @Override
354 | public void handleMessage(Message msg) {
355 | Context context = getContext();
356 | if (context == null) {
357 | Log.e(TAG, "error handling message, getContext() returned null");
358 | return;
359 | }
360 | switch (msg.arg1) {
361 | case COMMAND_CHANGE_TITLE:
362 | if (context instanceof Activity) {
363 | ((Activity) context).setTitle((String)msg.obj);
364 | } else {
365 | Log.e(TAG, "error handling message, getContext() returned no Activity");
366 | }
367 | break;
368 | case COMMAND_TEXTEDIT_HIDE:
369 | if (mTextEdit != null) {
370 | mTextEdit.setVisibility(View.GONE);
371 |
372 | InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
373 | imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0);
374 | }
375 | break;
376 | case COMMAND_SET_KEEP_SCREEN_ON:
377 | {
378 | Window window = ((Activity) context).getWindow();
379 | if (window != null) {
380 | if ((msg.obj instanceof Integer) && (((Integer) msg.obj).intValue() != 0)) {
381 | window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
382 | } else {
383 | window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
384 | }
385 | }
386 | break;
387 | }
388 | default:
389 | if ((context instanceof SDLActivity) && !((SDLActivity) context).onUnhandledMessage(msg.arg1, msg.obj)) {
390 | Log.e(TAG, "error handling message, command is " + msg.arg1);
391 | }
392 | }
393 | }
394 | }
395 |
396 | // Handler for the messages
397 | Handler commandHandler = new SDLCommandHandler();
398 |
399 | // Send a message from the SDLMain thread
400 | boolean sendCommand(int command, Object data) {
401 | Message msg = commandHandler.obtainMessage();
402 | msg.arg1 = command;
403 | msg.obj = data;
404 | return commandHandler.sendMessage(msg);
405 | }
406 |
407 | // C functions we call
408 | public static native int nativeInit(Object arguments);
409 | public static native void nativeLowMemory();
410 | public static native void nativeQuit();
411 | public static native void nativePause();
412 | public static native void nativeResume();
413 | public static native void onNativeDropFile(String filename);
414 | public static native void onNativeResize(int x, int y, int format, float rate);
415 | public static native int onNativePadDown(int device_id, int keycode);
416 | public static native int onNativePadUp(int device_id, int keycode);
417 | public static native void onNativeJoy(int device_id, int axis,
418 | float value);
419 | public static native void onNativeHat(int device_id, int hat_id,
420 | int x, int y);
421 | public static native void onNativeKeyDown(int keycode);
422 | public static native void onNativeKeyUp(int keycode);
423 | public static native void onNativeKeyboardFocusLost();
424 | public static native void onNativeMouse(int button, int action, float x, float y);
425 | public static native void onNativeTouch(int touchDevId, int pointerFingerId,
426 | int action, float x,
427 | float y, float p);
428 | public static native void onNativeAccel(float x, float y, float z);
429 | public static native void onNativeSurfaceChanged();
430 | public static native void onNativeSurfaceDestroyed();
431 | public static native int nativeAddJoystick(int device_id, String name,
432 | int is_accelerometer, int nbuttons,
433 | int naxes, int nhats, int nballs);
434 | public static native int nativeRemoveJoystick(int device_id);
435 | public static native String nativeGetHint(String name);
436 |
437 | /**
438 | * This method is called by SDL using JNI.
439 | */
440 | public static boolean setActivityTitle(String title) {
441 | // Called from SDLMain() thread and can't directly affect the view
442 | return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title);
443 | }
444 |
445 | /**
446 | * This method is called by SDL using JNI.
447 | */
448 | public static boolean sendMessage(int command, int param) {
449 | return mSingleton.sendCommand(command, Integer.valueOf(param));
450 | }
451 |
452 | /**
453 | * This method is called by SDL using JNI.
454 | */
455 | public static Context getContext() {
456 | return mSingleton;
457 | }
458 |
459 | /**
460 | * This method is called by SDL using JNI.
461 | * @return result of getSystemService(name) but executed on UI thread.
462 | */
463 | public Object getSystemServiceFromUiThread(final String name) {
464 | final Object lock = new Object();
465 | final Object[] results = new Object[2]; // array for writable variables
466 | synchronized (lock) {
467 | runOnUiThread(new Runnable() {
468 | @Override
469 | public void run() {
470 | synchronized (lock) {
471 | results[0] = getSystemService(name);
472 | results[1] = Boolean.TRUE;
473 | lock.notify();
474 | }
475 | }
476 | });
477 | if (results[1] == null) {
478 | try {
479 | lock.wait();
480 | } catch (InterruptedException ex) {
481 | ex.printStackTrace();
482 | }
483 | }
484 | }
485 | return results[0];
486 | }
487 |
488 | static class ShowTextInputTask implements Runnable {
489 | /*
490 | * This is used to regulate the pan&scan method to have some offset from
491 | * the bottom edge of the input region and the top edge of an input
492 | * method (soft keyboard)
493 | */
494 | static final int HEIGHT_PADDING = 15;
495 |
496 | public int x, y, w, h;
497 |
498 | public ShowTextInputTask(int x, int y, int w, int h) {
499 | this.x = x;
500 | this.y = y;
501 | this.w = w;
502 | this.h = h;
503 | }
504 |
505 | @Override
506 | public void run() {
507 | AbsoluteLayout.LayoutParams params = new AbsoluteLayout.LayoutParams(
508 | w, h + HEIGHT_PADDING, x, y);
509 |
510 | if (mTextEdit == null) {
511 | mTextEdit = new DummyEdit(getContext());
512 |
513 | mLayout.addView(mTextEdit, params);
514 | } else {
515 | mTextEdit.setLayoutParams(params);
516 | }
517 |
518 | mTextEdit.setVisibility(View.VISIBLE);
519 | mTextEdit.requestFocus();
520 |
521 | InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
522 | imm.showSoftInput(mTextEdit, 0);
523 | }
524 | }
525 |
526 | /**
527 | * This method is called by SDL using JNI.
528 | */
529 | public static boolean showTextInput(int x, int y, int w, int h) {
530 | // Transfer the task to the main thread as a Runnable
531 | return mSingleton.commandHandler.post(new ShowTextInputTask(x, y, w, h));
532 | }
533 |
534 | /**
535 | * This method is called by SDL using JNI.
536 | */
537 | public static Surface getNativeSurface() {
538 | return SDLActivity.mSurface.getNativeSurface();
539 | }
540 |
541 | // Audio
542 |
543 | /**
544 | * This method is called by SDL using JNI.
545 | */
546 | public static int audioInit(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) {
547 | int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO;
548 | int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT;
549 | int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1);
550 |
551 | Log.v(TAG, "SDL audio: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + (sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer");
552 |
553 | // Let the user pick a larger buffer if they really want -- but ye
554 | // gods they probably shouldn't, the minimums are horrifyingly high
555 | // latency already
556 | desiredFrames = Math.max(desiredFrames, (AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize);
557 |
558 | if (mAudioTrack == null) {
559 | mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate,
560 | channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM);
561 |
562 | // Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid
563 | // Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java
564 | // Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState()
565 |
566 | if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
567 | Log.e(TAG, "Failed during initialization of Audio Track");
568 | mAudioTrack = null;
569 | return -1;
570 | }
571 |
572 | mAudioTrack.play();
573 | }
574 |
575 | Log.v(TAG, "SDL audio: got " + ((mAudioTrack.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioTrack.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + (mAudioTrack.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer");
576 |
577 | return 0;
578 | }
579 |
580 | /**
581 | * This method is called by SDL using JNI.
582 | */
583 | public static void audioWriteShortBuffer(short[] buffer) {
584 | for (int i = 0; i < buffer.length; ) {
585 | int result = mAudioTrack.write(buffer, i, buffer.length - i);
586 | if (result > 0) {
587 | i += result;
588 | } else if (result == 0) {
589 | try {
590 | Thread.sleep(1);
591 | } catch(InterruptedException e) {
592 | // Nom nom
593 | }
594 | } else {
595 | Log.w(TAG, "SDL audio: error return from write(short)");
596 | return;
597 | }
598 | }
599 | }
600 |
601 | /**
602 | * This method is called by SDL using JNI.
603 | */
604 | public static void audioWriteByteBuffer(byte[] buffer) {
605 | for (int i = 0; i < buffer.length; ) {
606 | int result = mAudioTrack.write(buffer, i, buffer.length - i);
607 | if (result > 0) {
608 | i += result;
609 | } else if (result == 0) {
610 | try {
611 | Thread.sleep(1);
612 | } catch(InterruptedException e) {
613 | // Nom nom
614 | }
615 | } else {
616 | Log.w(TAG, "SDL audio: error return from write(byte)");
617 | return;
618 | }
619 | }
620 | }
621 |
622 | /**
623 | * This method is called by SDL using JNI.
624 | */
625 | public static void audioQuit() {
626 | if (mAudioTrack != null) {
627 | mAudioTrack.stop();
628 | mAudioTrack = null;
629 | }
630 | }
631 |
632 | // Input
633 |
634 | /**
635 | * This method is called by SDL using JNI.
636 | * @return an array which may be empty but is never null.
637 | */
638 | public static int[] inputGetInputDeviceIds(int sources) {
639 | int[] ids = InputDevice.getDeviceIds();
640 | int[] filtered = new int[ids.length];
641 | int used = 0;
642 | for (int i = 0; i < ids.length; ++i) {
643 | InputDevice device = InputDevice.getDevice(ids[i]);
644 | if ((device != null) && ((device.getSources() & sources) != 0)) {
645 | filtered[used++] = device.getId();
646 | }
647 | }
648 | return Arrays.copyOf(filtered, used);
649 | }
650 |
651 | // Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance
652 | public static boolean handleJoystickMotionEvent(MotionEvent event) {
653 | return mJoystickHandler.handleMotionEvent(event);
654 | }
655 |
656 | /**
657 | * This method is called by SDL using JNI.
658 | */
659 | public static void pollInputDevices() {
660 | if (SDLActivity.mSDLThread != null) {
661 | mJoystickHandler.pollInputDevices();
662 | }
663 | }
664 |
665 | // APK expansion files support
666 |
667 | /** com.android.vending.expansion.zipfile.ZipResourceFile object or null. */
668 | private Object expansionFile;
669 |
670 | /** com.android.vending.expansion.zipfile.ZipResourceFile's getInputStream() or null. */
671 | private Method expansionFileMethod;
672 |
673 | /**
674 | * This method was called by SDL using JNI.
675 | * @deprecated because of an incorrect name
676 | */
677 | @Deprecated
678 | public InputStream openAPKExtensionInputStream(String fileName) throws IOException {
679 | return openAPKExpansionInputStream(fileName);
680 | }
681 |
682 | /**
683 | * This method is called by SDL using JNI.
684 | * @return an InputStream on success or null if no expansion file was used.
685 | * @throws IOException on errors. Message is set for the SDL error message.
686 | */
687 | public InputStream openAPKExpansionInputStream(String fileName) throws IOException {
688 | // Get a ZipResourceFile representing a merger of both the main and patch files
689 | if (expansionFile == null) {
690 | String mainHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_MAIN_FILE_VERSION");
691 | if (mainHint == null) {
692 | return null; // no expansion use if no main version was set
693 | }
694 | String patchHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_PATCH_FILE_VERSION");
695 | if (patchHint == null) {
696 | return null; // no expansion use if no patch version was set
697 | }
698 |
699 | Integer mainVersion;
700 | Integer patchVersion;
701 | try {
702 | mainVersion = Integer.valueOf(mainHint);
703 | patchVersion = Integer.valueOf(patchHint);
704 | } catch (NumberFormatException ex) {
705 | ex.printStackTrace();
706 | throw new IOException("No valid file versions set for APK expansion files", ex);
707 | }
708 |
709 | try {
710 | // To avoid direct dependency on Google APK expansion library that is
711 | // not a part of Android SDK we access it using reflection
712 | expansionFile = Class.forName("com.android.vending.expansion.zipfile.APKExpansionSupport")
713 | .getMethod("getAPKExpansionZipFile", Context.class, int.class, int.class)
714 | .invoke(null, this, mainVersion, patchVersion);
715 |
716 | expansionFileMethod = expansionFile.getClass()
717 | .getMethod("getInputStream", String.class);
718 | } catch (Exception ex) {
719 | ex.printStackTrace();
720 | expansionFile = null;
721 | expansionFileMethod = null;
722 | throw new IOException("Could not access APK expansion support library", ex);
723 | }
724 | }
725 |
726 | // Get an input stream for a known file inside the expansion file ZIPs
727 | InputStream fileStream;
728 | try {
729 | fileStream = (InputStream)expansionFileMethod.invoke(expansionFile, fileName);
730 | } catch (Exception ex) {
731 | // calling "getInputStream" failed
732 | ex.printStackTrace();
733 | throw new IOException("Could not open stream from APK expansion file", ex);
734 | }
735 |
736 | if (fileStream == null) {
737 | // calling "getInputStream" was successful but null was returned
738 | throw new IOException("Could not find path in APK expansion file");
739 | }
740 |
741 | return fileStream;
742 | }
743 |
744 | // Messagebox
745 |
746 | /** Result of current messagebox. Also used for blocking the calling thread. */
747 | protected final int[] messageboxSelection = new int[1];
748 |
749 | /** Id of current dialog. */
750 | protected int dialogs = 0;
751 |
752 | /**
753 | * This method is called by SDL using JNI.
754 | * Shows the messagebox from UI thread and block calling thread.
755 | * buttonFlags, buttonIds and buttonTexts must have same length.
756 | * @param buttonFlags array containing flags for every button.
757 | * @param buttonIds array containing id for every button.
758 | * @param buttonTexts array containing text for every button.
759 | * @param colors null for default or array of length 5 containing colors.
760 | * @return button id or -1.
761 | */
762 | public int messageboxShowMessageBox(
763 | final int flags,
764 | final String title,
765 | final String message,
766 | final int[] buttonFlags,
767 | final int[] buttonIds,
768 | final String[] buttonTexts,
769 | final int[] colors) {
770 |
771 | messageboxSelection[0] = -1;
772 |
773 | // sanity checks
774 |
775 | if ((buttonFlags.length != buttonIds.length) && (buttonIds.length != buttonTexts.length)) {
776 | return -1; // implementation broken
777 | }
778 |
779 | // collect arguments for Dialog
780 |
781 | final Bundle args = new Bundle();
782 | args.putInt("flags", flags);
783 | args.putString("title", title);
784 | args.putString("message", message);
785 | args.putIntArray("buttonFlags", buttonFlags);
786 | args.putIntArray("buttonIds", buttonIds);
787 | args.putStringArray("buttonTexts", buttonTexts);
788 | args.putIntArray("colors", colors);
789 |
790 | // trigger Dialog creation on UI thread
791 |
792 | runOnUiThread(new Runnable() {
793 | @Override
794 | public void run() {
795 | showDialog(dialogs++, args);
796 | }
797 | });
798 |
799 | // block the calling thread
800 |
801 | synchronized (messageboxSelection) {
802 | try {
803 | messageboxSelection.wait();
804 | } catch (InterruptedException ex) {
805 | ex.printStackTrace();
806 | return -1;
807 | }
808 | }
809 |
810 | // return selected value
811 |
812 | return messageboxSelection[0];
813 | }
814 |
815 | @Override
816 | protected Dialog onCreateDialog(int ignore, Bundle args) {
817 |
818 | // TODO set values from "flags" to messagebox dialog
819 |
820 | // get colors
821 |
822 | int[] colors = args.getIntArray("colors");
823 | int backgroundColor;
824 | int textColor;
825 | int buttonBorderColor;
826 | int buttonBackgroundColor;
827 | int buttonSelectedColor;
828 | if (colors != null) {
829 | int i = -1;
830 | backgroundColor = colors[++i];
831 | textColor = colors[++i];
832 | buttonBorderColor = colors[++i];
833 | buttonBackgroundColor = colors[++i];
834 | buttonSelectedColor = colors[++i];
835 | } else {
836 | backgroundColor = Color.TRANSPARENT;
837 | textColor = Color.TRANSPARENT;
838 | buttonBorderColor = Color.TRANSPARENT;
839 | buttonBackgroundColor = Color.TRANSPARENT;
840 | buttonSelectedColor = Color.TRANSPARENT;
841 | }
842 |
843 | // create dialog with title and a listener to wake up calling thread
844 |
845 | final Dialog dialog = new Dialog(this);
846 | dialog.setTitle(args.getString("title"));
847 | dialog.setCancelable(false);
848 | dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
849 | @Override
850 | public void onDismiss(DialogInterface unused) {
851 | synchronized (messageboxSelection) {
852 | messageboxSelection.notify();
853 | }
854 | }
855 | });
856 |
857 | // create text
858 |
859 | TextView message = new TextView(this);
860 | message.setGravity(Gravity.CENTER);
861 | message.setText(args.getString("message"));
862 | if (textColor != Color.TRANSPARENT) {
863 | message.setTextColor(textColor);
864 | }
865 |
866 | // create buttons
867 |
868 | int[] buttonFlags = args.getIntArray("buttonFlags");
869 | int[] buttonIds = args.getIntArray("buttonIds");
870 | String[] buttonTexts = args.getStringArray("buttonTexts");
871 |
872 | final SparseArray