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 000000000..f331c5565 Binary files /dev/null and b/build/android/app/src/main/res/drawable/sad.png differ 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);