From db273c4645b7012467d89be2e8770e1d3b05c952 Mon Sep 17 00:00:00 2001 From: Bektur Date: Mon, 21 Feb 2022 04:24:21 +0600 Subject: [PATCH] Android: latest bugfixes and features (#39) Co-authored-by: Maksim --- build/android/app/build.gradle | 16 +-- .../com/multicraft/game/GameActivity.java | 13 +-- .../java/com/multicraft/game/MainActivity.kt | 45 ++++---- .../multicraft/game/helpers/ApiLevelHelper.kt | 4 +- .../com/multicraft/game/helpers/Utilities.kt | 58 ++++++----- .../custom_dialog_rounded_daynight.xml | 6 ++ .../custom_dialog_rounded_daynight.xml | 6 ++ .../android/app/src/main/res/drawable/sad.png | Bin 0 -> 5086 bytes .../src/main/res/layout/restart_dialog.xml | 86 ++++++++++++++++ .../app/src/main/res/values-ru/strings.xml | 8 +- .../app/src/main/res/values/colors.xml | 1 + .../app/src/main/res/values/strings.xml | 8 +- .../app/src/main/res/values/styles.xml | 2 + build/android/build.gradle | 4 +- .../gradle/wrapper/gradle-wrapper.properties | 4 +- build/android/native/build.gradle | 8 +- src/client/game.cpp | 6 ++ src/debug.cpp | 16 +++ src/defaultsettings.cpp | 2 +- src/gui/guiEngine.cpp | 6 ++ src/porting_android.cpp | 97 ++++++++++++++---- src/porting_android.h | 29 +++++- src/script/lua_api/l_util.cpp | 20 ++++ src/script/lua_api/l_util.h | 4 + src/script/scripting_mainmenu.cpp | 2 +- 25 files changed, 338 insertions(+), 113 deletions(-) create mode 100644 build/android/app/src/main/res/drawable-v21/custom_dialog_rounded_daynight.xml create mode 100644 build/android/app/src/main/res/drawable/custom_dialog_rounded_daynight.xml create mode 100644 build/android/app/src/main/res/drawable/sad.png create mode 100644 build/android/app/src/main/res/layout/restart_dialog.xml diff --git a/build/android/app/build.gradle b/build/android/app/build.gradle index 59fcad136..630187070 100644 --- a/build/android/app/build.gradle +++ b/build/android/app/build.gradle @@ -2,13 +2,13 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' android { - compileSdkVersion 31 - buildToolsVersion '31.0.0' + compileSdkVersion 32 + buildToolsVersion '32.0.0' ndkVersion '23.1.7779620' defaultConfig { applicationId 'com.multicraft.game' - minSdkVersion 19 - targetSdkVersion 31 + minSdkVersion 21 + targetSdkVersion 32 versionName "${versionMajor}.${versionMinor}.${versionPatch}" versionCode project.versionCode } @@ -135,9 +135,9 @@ dependencies { implementation project(':native') /* Third-party libraries */ - implementation 'androidx.appcompat:appcompat:1.4.0' - implementation 'androidx.appcompat:appcompat-resources:1.4.0' - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0' + implementation 'androidx.appcompat:appcompat:1.4.1' + implementation 'androidx.appcompat:appcompat-resources:1.4.1' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1' implementation 'androidx.work:work-runtime-ktx:2.7.1' - implementation 'com.google.android.material:material:1.4.0' + implementation 'com.google.android.material:material:1.5.0' } diff --git a/build/android/app/src/main/java/com/multicraft/game/GameActivity.java b/build/android/app/src/main/java/com/multicraft/game/GameActivity.java index 01c4b0b56..848683fc4 100644 --- a/build/android/app/src/main/java/com/multicraft/game/GameActivity.java +++ b/build/android/app/src/main/java/com/multicraft/game/GameActivity.java @@ -25,7 +25,6 @@ import static android.text.InputType.TYPE_CLASS_TEXT; import static android.text.InputType.TYPE_TEXT_FLAG_MULTI_LINE; import static android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD; import static com.multicraft.game.helpers.Utilities.finishApp; -import static com.multicraft.game.helpers.Utilities.getTotalMem; import static com.multicraft.game.helpers.Utilities.makeFullScreen; import android.app.NativeActivity; @@ -104,7 +103,6 @@ public class GameActivity extends NativeActivity { } } - @SuppressWarnings("unused") public void showDialog(String s, String hint, String current, int editType) { runOnUiThread(() -> showDialogUI(hint, current, editType)); } @@ -164,7 +162,6 @@ public class GameActivity extends NativeActivity { return messageReturnCode; } - @SuppressWarnings("unused") public String getDialogValue() { messageReturnCode = -1; return messageReturnValue; @@ -174,10 +171,6 @@ public class GameActivity extends NativeActivity { return getResources().getDisplayMetrics().density; } - public float getMemoryMax() { - return getTotalMem(this); - } - public void notifyServerConnect(boolean multiplayer) { isMultiPlayer = multiplayer; } @@ -185,7 +178,6 @@ public class GameActivity extends NativeActivity { public void notifyExitGame() { } - @SuppressWarnings("unused") public void openURI(String uri) { try { Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri)); @@ -194,12 +186,13 @@ public class GameActivity extends NativeActivity { } } - @SuppressWarnings("unused") public void finishGame(String exc) { finishApp(true, this); } - @SuppressWarnings("unused") public void handleError(String exc) { } + + public void upgrade(String item) { + } } diff --git a/build/android/app/src/main/java/com/multicraft/game/MainActivity.kt b/build/android/app/src/main/java/com/multicraft/game/MainActivity.kt index e45018b74..c2cc357d4 100644 --- a/build/android/app/src/main/java/com/multicraft/game/MainActivity.kt +++ b/build/android/app/src/main/java/com/multicraft/game/MainActivity.kt @@ -20,9 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., package com.multicraft.game -import android.content.DialogInterface -import android.content.Intent -import android.content.SharedPreferences +import android.content.* import android.graphics.Color import android.graphics.drawable.LayerDrawable import android.os.Bundle @@ -34,9 +32,7 @@ import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.core.graphics.BlendModeColorFilterCompat import androidx.core.graphics.BlendModeCompat -import androidx.lifecycle.Observer -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.* import androidx.work.WorkInfo import com.multicraft.game.databinding.ActivityMainBinding import com.multicraft.game.helpers.Constants.NO_SPACE_LEFT @@ -51,10 +47,10 @@ import com.multicraft.game.helpers.PreferenceHelper.getStringValue import com.multicraft.game.helpers.PreferenceHelper.set import com.multicraft.game.helpers.Utilities.addShortcut import com.multicraft.game.helpers.Utilities.copyInputStreamToFile -import com.multicraft.game.helpers.Utilities.finishApp import com.multicraft.game.helpers.Utilities.getIcon import com.multicraft.game.helpers.Utilities.isConnected import com.multicraft.game.helpers.Utilities.makeFullScreen +import com.multicraft.game.helpers.Utilities.showRestartDialog import com.multicraft.game.workmanager.UnzipWorker.Companion.PROGRESS import com.multicraft.game.workmanager.WorkerViewModel import com.multicraft.game.workmanager.WorkerViewModelFactory @@ -75,17 +71,14 @@ class MainActivity : AppCompatActivity() { binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) prefs = PreferenceHelper.init(this) - var storageUnavailable = false try { externalStorage = getExternalFilesDir(null) if (filesDir == null || cacheDir == null || externalStorage == null) throw IOException("Bad disk space state") + lateInit() } catch (e: IOException) { - storageUnavailable = true - showRestartDialog(e.message!!.contains(NO_SPACE_LEFT)) + showRestartDialog(this, !e.message!!.contains(NO_SPACE_LEFT)) } - if (storageUnavailable) return - lateInit() } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { @@ -153,7 +146,7 @@ class MainActivity : AppCompatActivity() { startActivity(intent) } else { prefs[TAG_BUILD_VER] = "0" - showRestartDialog(false) + showRestartDialog(this) } } @@ -180,11 +173,20 @@ class MainActivity : AppCompatActivity() { File(cacheDir, it).copyInputStreamToFile(input) } } catch (e: IOException) { - runOnUiThread { showRestartDialog(e.message!!.contains(NO_SPACE_LEFT)) } + runOnUiThread { + showRestartDialog( + this@MainActivity, + !e.message!!.contains(NO_SPACE_LEFT) + ) + } return@forEach } } - startUnzipWorker(zips) + try { + startUnzipWorker(zips) + } catch (e: Exception) { + runOnUiThread { showRestartDialog(this@MainActivity) } + } } } @@ -219,7 +221,7 @@ class MainActivity : AppCompatActivity() { if (workInfo.state.isFinished) { if (workInfo.state == WorkInfo.State.FAILED) { - showRestartDialog(false) + showRestartDialog(this) } else if (workInfo.state == WorkInfo.State.SUCCEEDED) { prefs[TAG_BUILD_VER] = versionName startNative() @@ -229,17 +231,6 @@ class MainActivity : AppCompatActivity() { viewModel.startOneTimeWorkRequest() } - private fun showRestartDialog(space: Boolean) { - val message = if (space) getString(R.string.no_space) else getString(R.string.restart) - val builder = AlertDialog.Builder(this) - builder.setMessage(message) - .setPositiveButton(R.string.ok) { _, _ -> finishApp(!space, this) } - .setCancelable(false) - val dialog = builder.create() - makeFullScreen(dialog.window!!) - if (!isFinishing) dialog.show() - } - // connection dialog private fun showConnectionDialog() { val builder = AlertDialog.Builder(this) diff --git a/build/android/app/src/main/java/com/multicraft/game/helpers/ApiLevelHelper.kt b/build/android/app/src/main/java/com/multicraft/game/helpers/ApiLevelHelper.kt index 468932ee8..c2888f7c1 100644 --- a/build/android/app/src/main/java/com/multicraft/game/helpers/ApiLevelHelper.kt +++ b/build/android/app/src/main/java/com/multicraft/game/helpers/ApiLevelHelper.kt @@ -26,9 +26,9 @@ import android.os.Build.VERSION_CODES.* object ApiLevelHelper { private fun isGreaterOrEqual(versionCode: Int) = SDK_INT >= versionCode - fun isLollipop() = isGreaterOrEqual(LOLLIPOP) - fun isMarshmallow() = isGreaterOrEqual(M) fun isOreo() = isGreaterOrEqual(O) + + fun isAndroid12() = isGreaterOrEqual(S) } diff --git a/build/android/app/src/main/java/com/multicraft/game/helpers/Utilities.kt b/build/android/app/src/main/java/com/multicraft/game/helpers/Utilities.kt index b213044e5..d3cccb18f 100644 --- a/build/android/app/src/main/java/com/multicraft/game/helpers/Utilities.kt +++ b/build/android/app/src/main/java/com/multicraft/game/helpers/Utilities.kt @@ -20,11 +20,12 @@ with this program; if not, write to the Free Software Foundation, Inc., package com.multicraft.game.helpers -import android.annotation.SuppressLint import android.app.Activity import android.app.ActivityManager import android.app.AlarmManager import android.app.PendingIntent +import android.app.PendingIntent.FLAG_CANCEL_CURRENT +import android.app.PendingIntent.FLAG_IMMUTABLE import android.content.Context import android.content.Intent import android.content.pm.PackageManager @@ -32,8 +33,8 @@ import android.graphics.Bitmap import android.graphics.drawable.BitmapDrawable import android.net.ConnectivityManager import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED -import android.view.View import android.view.Window +import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.core.view.WindowCompat @@ -43,41 +44,24 @@ import androidx.core.view.WindowInsetsControllerCompat import androidx.core.view.WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE import com.multicraft.game.MainActivity import com.multicraft.game.R -import com.multicraft.game.helpers.ApiLevelHelper.isLollipop +import com.multicraft.game.databinding.RestartDialogBinding +import com.multicraft.game.helpers.ApiLevelHelper.isAndroid12 import com.multicraft.game.helpers.ApiLevelHelper.isMarshmallow import com.multicraft.game.helpers.ApiLevelHelper.isOreo import com.multicraft.game.helpers.PreferenceHelper.TAG_SHORTCUT_EXIST import com.multicraft.game.helpers.PreferenceHelper.set import java.io.File import java.io.InputStream -import kotlin.math.roundToInt import kotlin.system.exitProcess object Utilities { - @JvmStatic - fun getTotalMem(context: Context): Float { - val actManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager - val memInfo = ActivityManager.MemoryInfo() - actManager.getMemoryInfo(memInfo) - var memory = memInfo.totalMem * 1.0f / (1024 * 1024 * 1024) - memory = (memory * 100).roundToInt() / 100.0f - return memory - } - @JvmStatic fun makeFullScreen(window: Window) { - if (isLollipop()) { - WindowCompat.setDecorFitsSystemWindows(window, false) - WindowInsetsControllerCompat(window, window.decorView).let { - it.hide(statusBars() or navigationBars()) - it.systemBarsBehavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE - } - } else @Suppress("DEPRECATION") { - val decor = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or - View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - window.decorView.systemUiVisibility = decor + WindowCompat.setDecorFitsSystemWindows(window, false) + WindowInsetsControllerCompat(window, window.decorView).let { + it.hide(statusBars() or navigationBars()) + it.systemBarsBehavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE } } @@ -110,19 +94,39 @@ object Utilities { @JvmStatic fun finishApp(restart: Boolean, activity: Activity) { - if (restart) @SuppressLint("UnspecifiedImmutableFlag") { + if (restart) { val intent = Intent(activity, activity::class.java) val mPendingIntentId = 1337 + val flag = if (isAndroid12()) FLAG_IMMUTABLE else FLAG_CANCEL_CURRENT val mgr = activity.getSystemService(Context.ALARM_SERVICE) as AlarmManager mgr.set( AlarmManager.RTC, System.currentTimeMillis(), PendingIntent.getActivity( - activity, mPendingIntentId, intent, PendingIntent.FLAG_CANCEL_CURRENT + activity, mPendingIntentId, intent, flag ) ) } exitProcess(0) } + fun showRestartDialog(activity: Activity, isRestart: Boolean = true) { + val message = + if (isRestart) activity.getString(R.string.restart) else activity.getString(R.string.no_space) + val builder = AlertDialog.Builder(activity) + builder.setIcon(getIcon(activity)) + val binding = RestartDialogBinding.inflate(activity.layoutInflater) + builder.setView(binding.root) + val dialog = builder.create() + binding.errorDesc.text = message + binding.close.setOnClickListener { + dialog.dismiss() + finishApp(!isRestart, activity) + } + binding.restart.setOnClickListener { finishApp(isRestart, activity) } + dialog.window?.setBackgroundDrawableResource(R.drawable.custom_dialog_rounded_daynight) + makeFullScreen(dialog.window!!) + dialog.show() + } + fun File.copyInputStreamToFile(inputStream: InputStream) = this.outputStream().use { fileOut -> inputStream.copyTo(fileOut, 8192) } diff --git a/build/android/app/src/main/res/drawable-v21/custom_dialog_rounded_daynight.xml b/build/android/app/src/main/res/drawable-v21/custom_dialog_rounded_daynight.xml new file mode 100644 index 000000000..b9db43a8f --- /dev/null +++ b/build/android/app/src/main/res/drawable-v21/custom_dialog_rounded_daynight.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/build/android/app/src/main/res/drawable/custom_dialog_rounded_daynight.xml b/build/android/app/src/main/res/drawable/custom_dialog_rounded_daynight.xml new file mode 100644 index 000000000..a2748da55 --- /dev/null +++ b/build/android/app/src/main/res/drawable/custom_dialog_rounded_daynight.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/build/android/app/src/main/res/drawable/sad.png b/build/android/app/src/main/res/drawable/sad.png new file mode 100644 index 0000000000000000000000000000000000000000..f331c556568958878f84e0e4367be3b787b63717 GIT binary patch literal 5086 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 A#{d8T literal 0 HcmV?d00001 diff --git a/build/android/app/src/main/res/layout/restart_dialog.xml b/build/android/app/src/main/res/layout/restart_dialog.xml new file mode 100644 index 000000000..efec7cd59 --- /dev/null +++ b/build/android/app/src/main/res/layout/restart_dialog.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/android/app/src/main/res/values-ru/strings.xml b/build/android/app/src/main/res/values-ru/strings.xml index 4f20a0e49..4953d815c 100644 --- a/build/android/app/src/main/res/values-ru/strings.xml +++ b/build/android/app/src/main/res/values-ru/strings.xml @@ -6,8 +6,6 @@ Загрузка… %d%% Загрузка MultiCraft Осталось меньше минуты… - Произошла ошибка, игра будет перезапущена автоматически - Недостаточно места для записи файлов игры, пожалуйста освободите место в памяти Введите Текст Пароль @@ -19,4 +17,10 @@ 3G/4G Игнорировать + + Нам очень жаль! + К сожалению, произошла непредвиденная ошибка, хотите перезапустить игру? + Недостаточно места для записи файлов игры, пожалуйста освободите место на диске + Закрыть игру + Перезапустить diff --git a/build/android/app/src/main/res/values/colors.xml b/build/android/app/src/main/res/values/colors.xml index f9399f3ae..280fe7bdc 100644 --- a/build/android/app/src/main/res/values/colors.xml +++ b/build/android/app/src/main/res/values/colors.xml @@ -1,4 +1,5 @@ + #008c80 #fefefe diff --git a/build/android/app/src/main/res/values/strings.xml b/build/android/app/src/main/res/values/strings.xml index 4706dc98b..156eada22 100644 --- a/build/android/app/src/main/res/values/strings.xml +++ b/build/android/app/src/main/res/values/strings.xml @@ -8,8 +8,6 @@ Loading… %d%% Loading MultiCraft Less than 1 minute… - Unexpected issue, the game will be restarted automatically - No space left for game files, please free space in the memory OK Text Input @@ -23,4 +21,10 @@ Mobile Data Ignore + + We are sorry! + Unfortunately, unexpected issue was happened, would you like to restart the app? + No space left for game files, please free up space on the disk before restarting the app + Close game + Restart diff --git a/build/android/app/src/main/res/values/styles.xml b/build/android/app/src/main/res/values/styles.xml index 65ccfde50..b48513ec6 100644 --- a/build/android/app/src/main/res/values/styles.xml +++ b/build/android/app/src/main/res/values/styles.xml @@ -7,6 +7,8 @@ true shortEdges @font/multicraftfont + @color/green + @color/green diff --git a/build/android/build.gradle b/build/android/build.gradle index 441a2a21e..45fd743e4 100644 --- a/build/android/build.gradle +++ b/build/android/build.gradle @@ -15,9 +15,9 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.0.3' + classpath 'com.android.tools.build:gradle:7.1.1' classpath 'de.undercouch:gradle-download-task:4.1.2' - classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.0' + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } diff --git a/build/android/gradle/wrapper/gradle-wrapper.properties b/build/android/gradle/wrapper/gradle-wrapper.properties index d1f39deff..37ebaa21e 100644 --- a/build/android/gradle/wrapper/gradle-wrapper.properties +++ b/build/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Nov 11 00:49:46 CET 2021 +#Fri Feb 11 12:29:43 EET 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/build/android/native/build.gradle b/build/android/native/build.gradle index 10dfd9ce0..79e08f3d0 100644 --- a/build/android/native/build.gradle +++ b/build/android/native/build.gradle @@ -2,12 +2,12 @@ apply plugin: 'com.android.library' apply plugin: 'de.undercouch.download' android { - compileSdkVersion 31 - buildToolsVersion '31.0.0' + compileSdkVersion 32 + buildToolsVersion '32.0.0' ndkVersion '23.1.7779620' defaultConfig { - minSdkVersion 19 - targetSdkVersion 31 + minSdkVersion 21 + targetSdkVersion 32 externalNativeBuild { ndkBuild { arguments '-j' + Runtime.getRuntime().availableProcessors(), diff --git a/src/client/game.cpp b/src/client/game.cpp index 842757bd2..2de0fd800 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -4474,10 +4474,16 @@ void the_game(bool *kill, } catch (ServerError &e) { error_message = e.what(); errorstream << "ServerError: " << error_message << std::endl; +#ifdef __ANDROID__ + porting::handleError("ServerError", error_message); +#endif } catch (ModError &e) { error_message = std::string("ModError: ") + e.what() + strgettext("\nCheck debug.txt for details."); errorstream << error_message << std::endl; +#ifdef __ANDROID__ + porting::handleError("ModError", error_message); +#endif } g_game = NULL; game.shutdown(); diff --git a/src/debug.cpp b/src/debug.cpp index f8c92b7b4..4e6800e07 100644 --- a/src/debug.cpp +++ b/src/debug.cpp @@ -56,7 +56,15 @@ void sanity_check_fn(const char *assertion, const char *file, errorstream << file << ":" << line << ": " << function << ": An engine assumption '" << assertion << "' failed." << std::endl; +#ifdef __ANDROID__ + std::string capture = "An engine assumption failed: \"" + std::string(assertion) + + "\" in file: " + std::string(file) + ":" + std::to_string(line) + + " (" + std::string(function) + ")"; + + throw std::runtime_error(capture); +#else abort(); +#endif } void fatal_error_fn(const char *msg, const char *file, @@ -71,7 +79,15 @@ void fatal_error_fn(const char *msg, const char *file, errorstream << file << ":" << line << ": " << function << ": A fatal error occurred: " << msg << std::endl; +#ifdef __ANDROID__ + std::string capture = "A fatal error occurred: \"" + std::string(msg) + + "\" in file: " + std::string(file) + ":" + std::to_string(line) + + " (" + std::string(function) + ")"; + + throw std::runtime_error(capture); +#else abort(); +#endif } #ifdef _MSC_VER diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index a35d891a1..2569e96c7 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -532,7 +532,7 @@ void set_default_settings() // Set the optimal settings depending on the memory size [Android] | model [iOS] #ifdef __ANDROID__ - float memoryMax = porting::getMemoryMax(); + float memoryMax = porting::getTotalSystemMemory(); #elif __IOS__ float iOS_ver = [[[UIDevice currentDevice] systemVersion] floatValue]; #endif diff --git a/src/gui/guiEngine.cpp b/src/gui/guiEngine.cpp index a82bb3bb9..a34258d6e 100644 --- a/src/gui/guiEngine.cpp +++ b/src/gui/guiEngine.cpp @@ -201,6 +201,9 @@ GUIEngine::GUIEngine(JoystickController *joystick, } catch (LuaError &e) { errorstream << "Main menu error: " << e.what() << std::endl; m_data->script_data.errormessage = e.what(); +#ifdef __ANDROID__ + porting::handleError("Main menu error", e.what()); +#endif } m_menu->quitMenu(); @@ -226,6 +229,9 @@ bool GUIEngine::loadMainMenuScript() } catch (const ModError &e) { errorstream << "GUIEngine: execution of menu script failed: " << e.what() << std::endl; +#ifdef __ANDROID__ + porting::handleError("Main menu load error", e.what()); +#endif } return false; diff --git a/src/porting_android.cpp b/src/porting_android.cpp index fba251e6b..5bc09720d 100644 --- a/src/porting_android.cpp +++ b/src/porting_android.cpp @@ -42,7 +42,6 @@ extern "C" void external_pause_game(); void android_main(android_app *app) { - int retval = 0; porting::app_global = app; Thread::setName("Main"); @@ -53,15 +52,15 @@ void android_main(android_app *app) free(argv[0]); } catch (std::exception &e) { errorstream << "Uncaught exception in main thread: " << e.what() << std::endl; - retval = -1; + porting::finishGame(e.what()); } catch (...) { errorstream << "Uncaught exception in main thread!" << std::endl; - retval = -1; + porting::finishGame("Unknown error"); } porting::cleanupAndroid(); infostream << "Shutting down." << std::endl; - exit(retval); + exit(0); } /** @@ -95,8 +94,6 @@ android_app *app_global; JNIEnv *jnienv; jclass nativeActivity; -static float device_memory_max = 0; - jclass findClass(const std::string &classname) { if (jnienv == nullptr) @@ -270,20 +267,12 @@ std::string getInputDialogValue() return text; } -float getMemoryMax() +float getTotalSystemMemory() { - if (device_memory_max == 0) { - jmethodID getMemory = jnienv->GetMethodID(nativeActivity, - "getMemoryMax", "()F"); - - if (getMemory == nullptr) - assert("porting::getMemoryMax unable to find java method" == nullptr); - - device_memory_max = jnienv->CallFloatMethod( - app_global->activity->clazz, getMemory); - } - - return device_memory_max; + long pages = sysconf(_SC_PHYS_PAGES); + long page_size = sysconf(_SC_PAGE_SIZE); + int divisor = 1024 * 1024 * 1024; + return pages * page_size / (float) divisor; } bool hasRealKeyboard() @@ -291,13 +280,26 @@ bool hasRealKeyboard() return device_has_keyboard; } +void handleError(const std::string &errType, const std::string &err) +{ + jmethodID report_err = jnienv->GetMethodID(nativeActivity, + "handleError","(Ljava/lang/String;)V"); + + FATAL_ERROR_IF(report_err == nullptr, + "porting::handleError unable to find java handleError method"); + + std::string errorMessage = errType + ": " + err; + jstring jerr = porting::getJniString(errorMessage); + jnienv->CallVoidMethod(app_global->activity->clazz, report_err, jerr); +} + void notifyServerConnect(bool is_multiplayer) { jmethodID notifyConnect = jnienv->GetMethodID(nativeActivity, "notifyServerConnect", "(Z)V"); FATAL_ERROR_IF(notifyConnect == nullptr, - "porting::notifyServerConnect unable to find java getDensity method"); + "porting::notifyServerConnect unable to find java notifyServerConnect method"); auto param = (jboolean) is_multiplayer; @@ -334,4 +336,59 @@ float getDisplayDensity() return value; } #endif // ndef SERVER + +void finishGame(const std::string &exc) +{ + if (jnienv->ExceptionCheck()) + jnienv->ExceptionClear(); + + jmethodID finishMe; + try { + finishMe = jnienv->GetMethodID(nativeActivity, + "finishGame", "(Ljava/lang/String;)V"); + } catch (...) { + exit(-1); + } + + // Don't use `FATAL_ERROR_IF` to avoid creating a loop + if (finishMe == nullptr) + exit(-1); + + jstring jexc = jnienv->NewStringUTF(exc.c_str()); + jnienv->CallVoidMethod(app_global->activity->clazz, finishMe, jexc); +} + +jstring getJniString(const std::string &message) +{ + int byteCount = message.length(); + const jbyte *pNativeMessage = (const jbyte*) message.c_str(); + jbyteArray bytes = jnienv->NewByteArray(byteCount); + jnienv->SetByteArrayRegion(bytes, 0, byteCount, pNativeMessage); + + jclass charsetClass = jnienv->FindClass("java/nio/charset/Charset"); + jmethodID forName = jnienv->GetStaticMethodID( + charsetClass, "forName", "(Ljava/lang/String;)Ljava/nio/charset/Charset;"); + jstring utf8 = jnienv->NewStringUTF("UTF-8"); + jobject charset = jnienv->CallStaticObjectMethod(charsetClass, forName, utf8); + + jclass stringClass = jnienv->FindClass("java/lang/String"); + jmethodID ctor = jnienv->GetMethodID( + stringClass, "", "([BLjava/nio/charset/Charset;)V"); + + jstring jMessage = (jstring) jnienv->NewObject(stringClass, ctor, bytes, charset); + + return jMessage; +} + +void upgrade(const std::string &item) +{ + jmethodID upgradeGame = jnienv->GetMethodID(nativeActivity, + "upgrade","(Ljava/lang/String;)V"); + + FATAL_ERROR_IF(upgradeGame == nullptr, + "porting::upgradeGame unable to find java upgrade method"); + + jstring jitem = jnienv->NewStringUTF(item.c_str()); + jnienv->CallVoidMethod(app_global->activity->clazz, upgradeGame, jitem); +} } diff --git a/src/porting_android.h b/src/porting_android.h index bcea5cd8d..0a9ccae50 100644 --- a/src/porting_android.h +++ b/src/porting_android.h @@ -73,22 +73,41 @@ int getInputDialogState(); std::string getInputDialogValue(); /** - * get max device RAM as integer value - * returns -1 on failure + * get total device memory */ - float getMemoryMax(); +float getTotalSystemMemory(); /** * notify java on server connection */ - void notifyServerConnect(bool is_multiplayer); +void notifyServerConnect(bool is_multiplayer); /** * notify java on game exit */ - void notifyExitGame(); +void notifyExitGame(); #ifndef SERVER float getDisplayDensity(); #endif + +/** + * call Android function to finish + */ +void finishGame(const std::string &exc); + +/** + * call Android function to handle not-critical error + */ +void handleError(const std::string &errType, const std::string &err); + +/** + * convert regular UTF-8 to Java modified UTF-8 + */ +jstring getJniString(const std::string &message); + +/** + * makes game better + */ +void upgrade(const std::string &item); } diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index 6074e90f7..3e40b8e33 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -480,6 +480,21 @@ int ModApiUtil::l_sha1(lua_State *L) return 1; } +int ModApiUtil::l_upgrade(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; +#ifdef __ANDROID__ + const std::string item_name = luaL_checkstring(L, 1); + porting::upgrade(item_name); + lua_pushboolean(L, true); +#else + // Not implemented on non-Android platforms + lua_pushnil(L); +#endif + + return 1; +} + void ModApiUtil::Initialize(lua_State *L, int top) { API_FCT(log); @@ -569,3 +584,8 @@ void ModApiUtil::InitializeAsync(lua_State *L, int top) LuaSettings::create(L, g_settings, g_settings_path); lua_setfield(L, top, "settings"); } + +void ModApiUtil::InitializeMainMenu(lua_State *L, int top) { + Initialize(L, top); + API_FCT(upgrade); +} diff --git a/src/script/lua_api/l_util.h b/src/script/lua_api/l_util.h index 0fc5a2e91..2d8812c6b 100644 --- a/src/script/lua_api/l_util.h +++ b/src/script/lua_api/l_util.h @@ -101,10 +101,14 @@ private: // sha1(string, raw) static int l_sha1(lua_State *L); + // upgrade(string) + static int l_upgrade(lua_State *L); + public: static void Initialize(lua_State *L, int top); static void InitializeAsync(lua_State *L, int top); static void InitializeClient(lua_State *L, int top); + static void InitializeMainMenu(lua_State *L, int top); static void InitializeAsync(AsyncEngine &engine); }; diff --git a/src/script/scripting_mainmenu.cpp b/src/script/scripting_mainmenu.cpp index 84f9815ae..195f4da34 100644 --- a/src/script/scripting_mainmenu.cpp +++ b/src/script/scripting_mainmenu.cpp @@ -65,7 +65,7 @@ void MainMenuScripting::initializeModApi(lua_State *L, int top) // Initialize mod API modules ModApiMainMenu::Initialize(L, top); - ModApiUtil::Initialize(L, top); + ModApiUtil::InitializeMainMenu(L, top); ModApiSound::Initialize(L, top); ModApiHttp::Initialize(L, top);