From dc0a259d6a599b52f2056665d071c92619abbe5f Mon Sep 17 00:00:00 2001 From: Mansour Moufid Date: Mon, 2 Sep 2019 12:30:57 -0400 Subject: [PATCH] Add Android project. --- chirpy-android/app/build.gradle | 41 +++ .../app/src/main/AndroidManifest.xml | 44 +++ .../app/src/main/res/values/styles.xml | 10 + chirpy-android/build.gradle | 29 ++ .../gradle/wrapper/gradle-wrapper.properties | 6 + chirpy-android/love/build.gradle | 33 ++ chirpy-android/love/src/jni/Application.mk | 19 ++ .../SDL2-2.0.7/android-project/build.gradle | 24 ++ .../java/org/love2d/android/GameActivity.java | 315 ++++++++++++++++++ 9 files changed, 521 insertions(+) create mode 100644 chirpy-android/app/build.gradle create mode 100644 chirpy-android/app/src/main/AndroidManifest.xml create mode 100644 chirpy-android/app/src/main/res/values/styles.xml create mode 100644 chirpy-android/build.gradle create mode 100644 chirpy-android/gradle/wrapper/gradle-wrapper.properties create mode 100644 chirpy-android/love/build.gradle create mode 100644 chirpy-android/love/src/jni/Application.mk create mode 100644 chirpy-android/love/src/jni/SDL2-2.0.7/android-project/build.gradle create mode 100644 chirpy-android/love/src/main/java/org/love2d/android/GameActivity.java diff --git a/chirpy-android/app/build.gradle b/chirpy-android/app/build.gradle new file mode 100644 index 0000000..c8ce57e --- /dev/null +++ b/chirpy-android/app/build.gradle @@ -0,0 +1,41 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion rootProject.ext.compileSdkVersion + buildToolsVersion rootProject.ext.buildToolsVersion + defaultConfig { + applicationId "com.eliteraspberries.chirpy" + versionCode 1 + versionName "0.1" + minSdkVersion rootProject.ext.minSdkVersion + ndk { + abiFilters rootProject.ext.abiFilters + } + resConfigs rootProject.ext.resConfigs + } + buildTypes { + debug { + debuggable true + minifyEnabled false + shrinkResources false + useProguard false + } + release { + debuggable false + minifyEnabled false + shrinkResources false + useProguard true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + lintOptions { + abortOnError false + } +} + +dependencies { + compile 'com.android.support:multidex:1.0.3' + compile fileTree(dir: 'libs', include: ['*.jar']) + compile "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}" + compile project(':love') +} diff --git a/chirpy-android/app/src/main/AndroidManifest.xml b/chirpy-android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..c325add --- /dev/null +++ b/chirpy-android/app/src/main/AndroidManifest.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/chirpy-android/app/src/main/res/values/styles.xml b/chirpy-android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..6102050 --- /dev/null +++ b/chirpy-android/app/src/main/res/values/styles.xml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/chirpy-android/build.gradle b/chirpy-android/build.gradle new file mode 100644 index 0000000..e0dff14 --- /dev/null +++ b/chirpy-android/build.gradle @@ -0,0 +1,29 @@ +buildscript { + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.2.0' + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} + +allprojects { + repositories { + google() + } +} + +ext { + compileSdkVersion = 28 + buildToolsVersion = '28.0.2' + supportLibVersion = '28.0.0' + abiFilters = 'armeabi-v7a' + minSdkVersion = 19 + targetSdkVersion = 19 + resConfigs = 'en' +} diff --git a/chirpy-android/gradle/wrapper/gradle-wrapper.properties b/chirpy-android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..7952071 --- /dev/null +++ b/chirpy-android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Sep 02 10:36:17 EDT 2019 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip diff --git a/chirpy-android/love/build.gradle b/chirpy-android/love/build.gradle new file mode 100644 index 0000000..362c9a7 --- /dev/null +++ b/chirpy-android/love/build.gradle @@ -0,0 +1,33 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion rootProject.ext.compileSdkVersion + buildToolsVersion rootProject.ext.buildToolsVersion + defaultConfig { + minSdkVersion rootProject.ext.minSdkVersion + ndk { + abiFilters rootProject.ext.abiFilters + } + resConfigs rootProject.ext.resConfigs + } + sourceSets { + main { + java { + srcDirs('src/main/java', 'src/jni/SDL2-2.0.7/android-project/app/src/main/java') + } + } + } + externalNativeBuild { + ndkBuild { + path "src/jni/Android.mk" + } + } + lintOptions { + abortOnError false + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + compile "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}" +} diff --git a/chirpy-android/love/src/jni/Application.mk b/chirpy-android/love/src/jni/Application.mk new file mode 100644 index 0000000..3d4c60b --- /dev/null +++ b/chirpy-android/love/src/jni/Application.mk @@ -0,0 +1,19 @@ +# Uncomment this if you're using STL in your project +# See CPLUSPLUS-SUPPORT.html in the NDK documentation for more information +# APP_STL := stlport_static +APP_STL := c++_shared +APP_ABI := armeabi-v7a +APP_CPPFLAGS += -DNDEBUG +APP_CFLAGS += -fpic -frtti -fwrapv +APP_CFLAGS += -Os +#APP_CFLAGS += -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv4 +#APP_CFLAGS += -mthumb +APP_LDFLAGS := -llog -landroid -lz +APP_PLATFORM := 19 +NDK_TOOLCHAIN_VERSION := clang + +# Fix for building on Windows +# http://stackoverflow.com/questions/12598933/ndk-build-createprocess-make-e-87-the-parameter-is-incorrect +APP_SHORT_COMMANDS := true + +# APP_OPTIM := debug diff --git a/chirpy-android/love/src/jni/SDL2-2.0.7/android-project/build.gradle b/chirpy-android/love/src/jni/SDL2-2.0.7/android-project/build.gradle new file mode 100644 index 0000000..02582d3 --- /dev/null +++ b/chirpy-android/love/src/jni/SDL2-2.0.7/android-project/build.gradle @@ -0,0 +1,24 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + jcenter() + google() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.2.0' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/chirpy-android/love/src/main/java/org/love2d/android/GameActivity.java b/chirpy-android/love/src/main/java/org/love2d/android/GameActivity.java new file mode 100644 index 0000000..eee9554 --- /dev/null +++ b/chirpy-android/love/src/main/java/org/love2d/android/GameActivity.java @@ -0,0 +1,315 @@ +package org.love2d.android; + +import org.libsdl.app.SDLActivity; + +import java.util.Arrays; +import java.util.List; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.DownloadManager; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.media.AudioManager; +import android.media.AudioRecord; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.os.Handler; +import android.os.PowerManager; +import android.os.ResultReceiver; +import android.os.Vibrator; +import android.support.annotation.Keep; +import android.util.Log; +import android.util.DisplayMetrics; +import android.widget.Toast; +import android.view.*; +import android.content.pm.PackageManager; + +public class GameActivity extends SDLActivity { + private static DisplayMetrics metrics = new DisplayMetrics(); + private static String gamePath = ""; + private static Context context; + private static Vibrator vibrator = null; + private static boolean immersiveActive = false; + private static boolean mustCacheArchive = false; + + @Override + protected String[] getLibraries() { + return new String[]{ + "c++_shared", + "mpg123", + "openal", + "love", + }; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + Log.d("GameActivity", "started"); + + context = this.getApplicationContext(); + + String permission = "android.permission.VIBRATE"; + int res = context.checkCallingOrSelfPermission(permission); + if (res == PackageManager.PERMISSION_GRANTED) { + vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); + } else { + Log.d("GameActivity", "Vibration disabled: could not get vibration permission."); + } + + handleIntent(this.getIntent()); + + super.onCreate(savedInstanceState); + getWindowManager().getDefaultDisplay().getMetrics(metrics); + } + + @Override + protected void onNewIntent(Intent intent) { + Log.d("GameActivity", "onNewIntent() with " + intent); + handleIntent(intent); + resetNative(); + startNative(); + } + + protected void handleIntent(Intent intent) { + Uri game = intent.getData(); + + if (game != null) { + // If we have a game via the intent data we we try to figure out how we have to load it. We + // support the following variations: + // * a main.lua file: set gamePath to the directory containing main.lua + // * otherwise: set gamePath to the file + if (game.getScheme().equals("file")) { + Log.d("GameActivity", "Received intent with path: " + game.getPath()); + // If we were given the path of a main.lua then use its + // directory. Otherwise use full path. + List path_segments = game.getPathSegments(); + if (path_segments.get(path_segments.size() - 1).equals("main.lua")) { + gamePath = game.getPath().substring(0, game.getPath().length() - "main.lua".length()); + } else { + gamePath = game.getPath(); + } + } else { + Log.e("GameActivity", "Unsupported scheme: '" + game.getScheme() + "'."); + + AlertDialog.Builder alert_dialog = new AlertDialog.Builder(this); + alert_dialog.setMessage("Could not load LÖVE game '" + game.getPath() + + "' as it uses unsupported scheme '" + game.getScheme() + + "'. Please contact the developer."); + alert_dialog.setTitle("LÖVE for Android Error"); + alert_dialog.setPositiveButton("Exit", + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + finish(); + } + }); + alert_dialog.setCancelable(false); + alert_dialog.create().show(); + } + } else { + // No game specified via the intent data -> check whether we have a game.love in our assets. + boolean game_love_in_assets = false; + try { + List assets = Arrays.asList(getAssets().list("")); + game_love_in_assets = assets.contains("game.love"); + } catch (Exception e) { + Log.d("GameActivity", "could not list application assets:" + e.getMessage()); + } + + if (game_love_in_assets) { + // If we have a game.love in our assets folder copy it to the cache folder + // so that we can load it from native LÖVE code + String destination_file = this.getCacheDir().getPath() + "/game.love"; + if (mustCacheArchive && copyAssetFile("game.love", destination_file)) + gamePath = destination_file; + else + gamePath = "game.love"; + } else { + // If no game.love was found fall back to the game in /lovegame + File ext = Environment.getExternalStorageDirectory(); + if ((new File(ext, "/lovegame/main.lua")).exists()) { + gamePath = ext.getPath() + "/lovegame/"; + } + } + } + + Log.d("GameActivity", "new gamePath: " + gamePath); + } + + @Override + protected void onDestroy() { + if (vibrator != null) { + Log.d("GameActivity", "Cancelling vibration"); + vibrator.cancel(); + } + super.onDestroy(); + } + + @Override + protected void onPause() { + if (vibrator != null) { + Log.d("GameActivity", "Cancelling vibration"); + vibrator.cancel(); + } + super.onPause(); + } + + @Override + public void onResume() { + super.onResume(); + + if (immersiveActive) { + getWindow().getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + } + } + + public void setImmersiveMode(boolean immersive_mode) { + if (android.os.Build.VERSION.SDK_INT < 11) { + // The API getWindow().getDecorView().setSystemUiVisibility() was + // added in Android 11 (a.k.a. Honeycomb, a.k.a. 3.0.x). If we run + // on this we do nothing. + return; + } + + immersiveActive = immersive_mode; + + final Object lock = new Object(); + final boolean immersive_enabled = immersive_mode; + synchronized (lock) { + runOnUiThread(new Runnable() { + @Override + public void run() { + synchronized (lock) { + if (immersive_enabled) { + getWindow().getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + } else { + getWindow().getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + ); + } + + lock.notify(); + } + } + }); + } + ; + } + + public boolean getImmersiveMode() { + return immersiveActive; + } + + public static String getGamePath() { + Log.d("GameActivity", "called getGamePath(), game path = " + gamePath); + return gamePath; + } + + public static DisplayMetrics getMetrics() { + return metrics; + } + + public static void vibrate(double seconds) { + if (vibrator != null) { + vibrator.vibrate((long) (seconds * 1000.)); + } + } + + public static void openURL(String url) { + Log.d("GameActivity", "opening url = " + url); + Intent i = new Intent(Intent.ACTION_VIEW); + i.setData(Uri.parse(url)); + i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(i); + } + + /** + * Copies a given file from the assets folder to the destination. + * + * @return true if successful + */ + boolean copyAssetFile(String fileName, String destinationFileName) { + boolean success = false; + + // open source and destination streams + InputStream source_stream = null; + try { + source_stream = getAssets().open(fileName); + } catch (IOException e) { + Log.d("GameActivity", "Could not open game.love from assets: " + e.getMessage()); + } + + BufferedOutputStream destination_stream = null; + try { + destination_stream = new BufferedOutputStream(new FileOutputStream(destinationFileName, false)); + } catch (IOException e) { + Log.d("GameActivity", "Could not open destination file: " + e.getMessage()); + } + + // perform the copying + int chunk_read = 0; + int bytes_written = 0; + + assert (source_stream != null && destination_stream != null); + + try { + byte[] buf = new byte[1024]; + chunk_read = source_stream.read(buf); + do { + destination_stream.write(buf, 0, chunk_read); + bytes_written += chunk_read; + chunk_read = source_stream.read(buf); + } while (chunk_read != -1); + } catch (IOException e) { + Log.d("GameActivity", "Copying failed:" + e.getMessage()); + } + + // close streams + try { + if (source_stream != null) source_stream.close(); + if (destination_stream != null) destination_stream.close(); + success = true; + } catch (IOException e) { + Log.d("GameActivity", "Copying failed: " + e.getMessage()); + } + + Log.d("GameActivity", "Successfully copied " + fileName + + " to " + destinationFileName + + " (" + bytes_written + " bytes written)."); + return success; + } + + @Keep + public boolean hasBackgroundMusic() { + AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + return audioManager.isMusicActive(); + } +}