diff --git a/.gitignore b/.gitignore index 5d05b6dcb..4a95b31db 100644 --- a/.gitignore +++ b/.gitignore @@ -25,4 +25,5 @@ local.properties # Gradle build files build -.externalNativeBuild \ No newline at end of file +.externalNativeBuild +.cxx diff --git a/README.md b/README.md index c41ddc1c3..f740dfe9b 100644 --- a/README.md +++ b/README.md @@ -33,27 +33,24 @@ In the repository directory. For the last command, add `--depth 1` if needed. Instructions: ------------- -Detailed instructions can be viewed at https://bitbucket.org/MartinFelis/love-android-sdl2/wiki/Home (at the moment) +Detailed instructions can be viewed at https://github.com/love2d/love-android/wiki Quick Start: ------------ -Install the Android NDK and the Android SDK with SDK API 28, set the -environment variables: +Install the Android SDK with SDK API 30 and Android NDK 21.3.6528147, set the environment variables: * `ANDROID_HOME` to your Android SDK location. -* `ANDROID_NDK_HOME` to your Android NDK location. NDK r16 or later! - (you may have to adjust the paths to the install directories of the Android -SDK and Android NDK on your system) and run +SDK on your system) and run ``` ./gradlew assembleNormal ``` -in the root folder of this project. This should give you a .apk file in the app/build/outputs/apk/normal -subdirectory that you can then sign and install on your phone. The .apk flavor is what you normally have +in the root folder of this project. This should give you a .apk file in the `app/build/outputs/apk/normal` +subdirectory that you can then sign and install on your phone. The `normal` .apk flavor is what you normally have when downloading one from love2d.org. If you want to build the ["embed"](https://love2d.org/wiki/Game_Distribution/APKTool APK, change `assembleNormal` to `assembleEmbedRelease` instead at command above. @@ -63,7 +60,7 @@ and on the tab "SDK Tools", select NDK. After that, open the repository root. Bugs: ----- -Bugs and feature requests should be reported to the issue tracker at https://bitbucket.org/MartinFelis/love-android-sdl2/issues?status=new&status=open +Bugs and feature requests should be reported to the issue tracker at https://github.com/love2d/love-android/issues Changelog: ---------- @@ -72,7 +69,7 @@ Changelog: * Contains all relevant changes for desktop LÖVE [11.3](https://love2d.org/wiki/11.3). * Added support for microphone recording on Android. **This is disabled in Play Store builds**. -* Added t.audio.mic (false by default). On Android, setting it to true requests microphone recording permission from the user. +* Added t.audio.mic (`false` by default). On Android, setting it to true requests microphone recording permission from the user. * Fixed performance regression on Android devices with Adreno GPU. * Fixed video playback support on Android devices with Adreno GPU. diff --git a/app/build.gradle b/app/build.gradle index 394eadd5c..e9eed6df9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,14 +1,15 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 28 - buildToolsVersion "28.0.3" + compileSdkVersion 30 + buildToolsVersion '30.0.2' + ndkVersion '21.3.6528147' defaultConfig { - applicationId "org.love2d.android" + applicationId 'org.love2d.android' versionCode 29 - versionName "11.3a" + versionName '11.3a' minSdkVersion 14 - targetSdkVersion 28 + targetSdkVersion 30 } buildTypes { release { @@ -26,7 +27,7 @@ android { } embed { dimension 'mode' - applicationIdSuffix "embed" + applicationIdSuffix '.embed' } } lintOptions { @@ -35,8 +36,8 @@ android { } dependencies { - api 'androidx.multidex:multidex:2.0.0' + api 'androidx.multidex:multidex:2.0.1' api fileTree(dir: 'libs', include: ['*.jar']) - api 'androidx.appcompat:appcompat:1.1.0-alpha01' + api 'androidx.appcompat:appcompat:1.2.0' api project(':love') } diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 055817141..df2aff3bb 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -16,8 +16,4 @@ # public *; #} --keep class br.com.tapps.love.LoveActivity { *; } - --keepclassmembers class br.com.tapps.love.LoveActivity { - public com.naef.jnlua.LuaState createLuaState(); -} \ No newline at end of file +-keep class org.love2d.android.GameActivity { *; } diff --git a/app/src/embed/AndroidManifest.xml b/app/src/embed/AndroidManifest.xml index 263a4429b..f6c0d1765 100644 --- a/app/src/embed/AndroidManifest.xml +++ b/app/src/embed/AndroidManifest.xml @@ -12,7 +12,8 @@ android:icon="@drawable/love" android:label="LÖVE for Android" android:usesCleartextTraffic="true" - tools:node="replace" > + android:hardwareAccelerated="true" + tools:node="replace" > - - + + + - + + + + + + + + + + android:usesCleartextTraffic="true" + android:hardwareAccelerated="true" > @@ -43,7 +53,6 @@ - @@ -51,9 +60,13 @@ - + + + + + \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -30,6 +48,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,26 +59,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -85,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then @@ -150,11 +154,19 @@ if $cygwin ; then esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 8a0b282aa..e95643d6a 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,90 +1,84 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/love/build.gradle b/love/build.gradle index 43a657868..c7643276c 100644 --- a/love/build.gradle +++ b/love/build.gradle @@ -6,14 +6,15 @@ android { // See https://code.google.com/p/android/issues/detail?id=52962 // and http://stackoverflow.com/questions/27277433/why-does-gradle-build-my-module-in-release-mode-when-the-app-is-in-debug // defaultPublishConfig "debug" - compileSdkVersion 28 - buildToolsVersion '28.0.3' + compileSdkVersion 30 + buildToolsVersion '30.0.2' + ndkVersion '21.3.6528147' defaultConfig { - compileSdkVersion 28 - buildToolsVersion "28.0.3" + compileSdkVersion 30 + buildToolsVersion "30.0.2" minSdkVersion 14 - resValue "bool", "embed", "false" + resValue 'bool', 'embed', 'false' externalNativeBuild { ndkBuild { arguments "-j4" @@ -24,6 +25,7 @@ android { // libraries Gradle should build and package with your APK. abiFilters 'armeabi-v7a', 'arm64-v8a' } + targetSdkVersion 30 } buildTypes { release { @@ -41,7 +43,7 @@ android { } embed { dimension 'mode' - resValue "bool", "embed", "true" + resValue 'bool', 'embed', 'true' } } sourceSets { @@ -59,7 +61,7 @@ android { } externalNativeBuild { ndkBuild { - path "src/jni/Android.mk" + path 'src/jni/Android.mk' } } lintOptions { @@ -73,5 +75,5 @@ android { dependencies { api fileTree(dir: 'libs', include: ['*.jar']) - api 'androidx.appcompat:appcompat:1.1.0-alpha01' + api 'androidx.appcompat:appcompat:1.2.0' } diff --git a/love/src/jni/SDL2/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java b/love/src/jni/SDL2/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java index 3633670c1..a5529792f 100644 --- a/love/src/jni/SDL2/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java +++ b/love/src/jni/SDL2/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java @@ -243,6 +243,8 @@ public void onClick(DialogInterface dialog,int id) { // love2d-mod-start: allow restarting of the native thread public void startNative() { + boolean hadSDLThread = SDLActivity.mSDLThread != null; + // Set up JNI SDL.setupJNI(); @@ -283,6 +285,10 @@ public void startNative() { SDLActivity.onNativeDropFile(filename); } } + + if (hadSDLThread) { + resumeNativeThread(); + } } // love2d-mod-end: allow restarting of the native thread diff --git a/love/src/main/java/org/love2d/android/GameActivity.java b/love/src/main/java/org/love2d/android/GameActivity.java index edda34a93..eff619e72 100644 --- a/love/src/main/java/org/love2d/android/GameActivity.java +++ b/love/src/main/java/org/love2d/android/GameActivity.java @@ -38,7 +38,6 @@ import android.media.AudioManager; import android.net.Uri; import android.os.Bundle; -import android.os.Environment; import android.os.Vibrator; import android.util.Log; import android.util.DisplayMetrics; @@ -112,6 +111,7 @@ protected void onCreate(Bundle savedInstanceState) { storagePermissionUnnecessary = false; embed = context.getResources().getBoolean(R.bool.embed); + // Get filename from "Open with" of another application handleIntent(this.getIntent()); super.onCreate(savedInstanceState); @@ -134,7 +134,17 @@ protected void onNewIntent(Intent intent) { } protected void handleIntent(Intent intent) { - Uri game = intent.getData(); + Uri game = null; + + // Try to handle "Share" intent. + // This is actually "bit tricky" to get working in user phone + // because shared static variables issue in the native side + // (user have to clear LOVE for Android in their recent apps list). + if (Intent.ACTION_SEND.equals(intent.getAction())) { + game = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM); + } else { + game = intent.getData(); + } if (!embed && game != null) { String scheme = game.getScheme(); @@ -229,17 +239,13 @@ public void onClick(DialogInterface dialog, int id) { } protected void checkLovegameFolder() { - // If no game.love was found fall back to the game in /lovegame - // if using normal or playstore build + // If no game.love was found and embed flavor is not used, fall back to the game in + // /Android/data//games/lovegame if (!embed) { Log.d("GameActivity", "fallback to lovegame folder"); - if (hasExternalStoragePermission()) { - File ext = Environment.getExternalStorageDirectory(); - if ((new File(ext, "/lovegame/main.lua")).exists()) { - gamePath = ext.getPath() + "/lovegame/"; - } - } else { - Log.d("GameActivity", "Cannot load game from /sdcard/lovegame: permission not granted"); + File ext = getExternalFilesDir("games"); + if ((new File(ext, "/lovegame/main.lua")).exists()) { + gamePath = ext.getPath() + "/lovegame/"; } } } @@ -295,7 +301,6 @@ public static String getGamePath() { } else { Log.d("GameActivity", "cannot open game " + gamePath + ": no external storage permission given!"); } - } else { self.checkLovegameFolder(); if (gamePath.length() > 0) diff --git a/love/src/normal/java/org/love2d/android/DownloadActivity.java b/love/src/normal/java/org/love2d/android/DownloadActivity.java index f7009764f..4ba814dc9 100644 --- a/love/src/normal/java/org/love2d/android/DownloadActivity.java +++ b/love/src/normal/java/org/love2d/android/DownloadActivity.java @@ -27,6 +27,7 @@ import android.content.pm.PackageManager; import android.Manifest; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.util.Log; @@ -37,7 +38,7 @@ public class DownloadActivity extends Activity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (android.os.Build.VERSION.SDK_INT >= 29 || + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q || ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { runDownloader(); diff --git a/love/src/normal/java/org/love2d/android/DownloadService.java b/love/src/normal/java/org/love2d/android/DownloadService.java index 1d83d6efd..8ce91ad0a 100644 --- a/love/src/normal/java/org/love2d/android/DownloadService.java +++ b/love/src/normal/java/org/love2d/android/DownloadService.java @@ -60,14 +60,14 @@ protected void onHandleIntent(Intent intent) { request.setTitle(uri.getLastPathSegment()); request.setMimeType("application/x-love-game"); - // in order for this if to run, you must use the android 3.2 to compile your app - if (Build.VERSION.SDK_INT >= 11) { - DownloadRequestSettings_API11 settings = new DownloadRequestSettings_API11(); - settings.setup(request); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + request.allowScanningByMediaScanner(); } + + request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, uri.getLastPathSegment()); - // get download service and enqueue file + // get download service and enqueue file Log.d("DownloadActivity", "creating manager"); DownloadManager manager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE); Log.d("DownloadActivity", "enqueuing download"); @@ -78,26 +78,6 @@ protected void onHandleIntent(Intent intent) { registerReceiver(downloadReceiver, intentFilter); } - /** - * @param context used to check the device version and DownloadManager information - * @return true if the download manager is available - */ - public static boolean isDownloadManagerAvailable(Context context) { - try { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) { - return false; - } - Intent intent = new Intent(Intent.ACTION_MAIN); - intent.addCategory(Intent.CATEGORY_LAUNCHER); - intent.setClassName("com.android.providers.downloads.ui", "com.android.providers.downloads.ui.DownloadList"); - List list = context.getPackageManager().queryIntentActivities(intent, - PackageManager.MATCH_DEFAULT_ONLY); - return list.size() > 0; - } catch (Exception e) { - return false; - } - } - private BroadcastReceiver downloadReceiver = new BroadcastReceiver() { @Override @@ -107,10 +87,3 @@ public void onReceive(Context context, Intent intent) { } }; } - -class DownloadRequestSettings_API11 { - public static void setup(DownloadManager.Request request) { - request.allowScanningByMediaScanner(); - request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); - } -}