Android: latest bugfixes and features (#39)

Co-authored-by: Maksim <MoNTE48@mail.ua>
This commit is contained in:
Bektur 2022-02-21 04:24:21 +06:00 committed by Maksim
parent e03170fb77
commit db273c4645
25 changed files with 338 additions and 113 deletions

View File

@ -2,13 +2,13 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
android { android {
compileSdkVersion 31 compileSdkVersion 32
buildToolsVersion '31.0.0' buildToolsVersion '32.0.0'
ndkVersion '23.1.7779620' ndkVersion '23.1.7779620'
defaultConfig { defaultConfig {
applicationId 'com.multicraft.game' applicationId 'com.multicraft.game'
minSdkVersion 19 minSdkVersion 21
targetSdkVersion 31 targetSdkVersion 32
versionName "${versionMajor}.${versionMinor}.${versionPatch}" versionName "${versionMajor}.${versionMinor}.${versionPatch}"
versionCode project.versionCode versionCode project.versionCode
} }
@ -135,9 +135,9 @@ dependencies {
implementation project(':native') implementation project(':native')
/* Third-party libraries */ /* Third-party libraries */
implementation 'androidx.appcompat:appcompat:1.4.0' implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'androidx.appcompat:appcompat-resources:1.4.0' implementation 'androidx.appcompat:appcompat-resources:1.4.1'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1'
implementation 'androidx.work:work-runtime-ktx:2.7.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'
} }

View File

@ -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_FLAG_MULTI_LINE;
import static android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD; import static android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD;
import static com.multicraft.game.helpers.Utilities.finishApp; 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 static com.multicraft.game.helpers.Utilities.makeFullScreen;
import android.app.NativeActivity; 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) { public void showDialog(String s, String hint, String current, int editType) {
runOnUiThread(() -> showDialogUI(hint, current, editType)); runOnUiThread(() -> showDialogUI(hint, current, editType));
} }
@ -164,7 +162,6 @@ public class GameActivity extends NativeActivity {
return messageReturnCode; return messageReturnCode;
} }
@SuppressWarnings("unused")
public String getDialogValue() { public String getDialogValue() {
messageReturnCode = -1; messageReturnCode = -1;
return messageReturnValue; return messageReturnValue;
@ -174,10 +171,6 @@ public class GameActivity extends NativeActivity {
return getResources().getDisplayMetrics().density; return getResources().getDisplayMetrics().density;
} }
public float getMemoryMax() {
return getTotalMem(this);
}
public void notifyServerConnect(boolean multiplayer) { public void notifyServerConnect(boolean multiplayer) {
isMultiPlayer = multiplayer; isMultiPlayer = multiplayer;
} }
@ -185,7 +178,6 @@ public class GameActivity extends NativeActivity {
public void notifyExitGame() { public void notifyExitGame() {
} }
@SuppressWarnings("unused")
public void openURI(String uri) { public void openURI(String uri) {
try { try {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri)); 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) { public void finishGame(String exc) {
finishApp(true, this); finishApp(true, this);
} }
@SuppressWarnings("unused")
public void handleError(String exc) { public void handleError(String exc) {
} }
public void upgrade(String item) {
}
} }

View File

@ -20,9 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
package com.multicraft.game package com.multicraft.game
import android.content.DialogInterface import android.content.*
import android.content.Intent
import android.content.SharedPreferences
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.LayerDrawable import android.graphics.drawable.LayerDrawable
import android.os.Bundle import android.os.Bundle
@ -34,9 +32,7 @@ import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.graphics.BlendModeColorFilterCompat import androidx.core.graphics.BlendModeColorFilterCompat
import androidx.core.graphics.BlendModeCompat import androidx.core.graphics.BlendModeCompat
import androidx.lifecycle.Observer import androidx.lifecycle.*
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.work.WorkInfo import androidx.work.WorkInfo
import com.multicraft.game.databinding.ActivityMainBinding import com.multicraft.game.databinding.ActivityMainBinding
import com.multicraft.game.helpers.Constants.NO_SPACE_LEFT 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.PreferenceHelper.set
import com.multicraft.game.helpers.Utilities.addShortcut import com.multicraft.game.helpers.Utilities.addShortcut
import com.multicraft.game.helpers.Utilities.copyInputStreamToFile 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.getIcon
import com.multicraft.game.helpers.Utilities.isConnected import com.multicraft.game.helpers.Utilities.isConnected
import com.multicraft.game.helpers.Utilities.makeFullScreen 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.UnzipWorker.Companion.PROGRESS
import com.multicraft.game.workmanager.WorkerViewModel import com.multicraft.game.workmanager.WorkerViewModel
import com.multicraft.game.workmanager.WorkerViewModelFactory import com.multicraft.game.workmanager.WorkerViewModelFactory
@ -75,17 +71,14 @@ class MainActivity : AppCompatActivity() {
binding = ActivityMainBinding.inflate(layoutInflater) binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
prefs = PreferenceHelper.init(this) prefs = PreferenceHelper.init(this)
var storageUnavailable = false
try { try {
externalStorage = getExternalFilesDir(null) externalStorage = getExternalFilesDir(null)
if (filesDir == null || cacheDir == null || externalStorage == null) if (filesDir == null || cacheDir == null || externalStorage == null)
throw IOException("Bad disk space state") throw IOException("Bad disk space state")
lateInit()
} catch (e: IOException) { } catch (e: IOException) {
storageUnavailable = true showRestartDialog(this, !e.message!!.contains(NO_SPACE_LEFT))
showRestartDialog(e.message!!.contains(NO_SPACE_LEFT))
} }
if (storageUnavailable) return
lateInit()
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
@ -153,7 +146,7 @@ class MainActivity : AppCompatActivity() {
startActivity(intent) startActivity(intent)
} else { } else {
prefs[TAG_BUILD_VER] = "0" prefs[TAG_BUILD_VER] = "0"
showRestartDialog(false) showRestartDialog(this)
} }
} }
@ -180,11 +173,20 @@ class MainActivity : AppCompatActivity() {
File(cacheDir, it).copyInputStreamToFile(input) File(cacheDir, it).copyInputStreamToFile(input)
} }
} catch (e: IOException) { } catch (e: IOException) {
runOnUiThread { showRestartDialog(e.message!!.contains(NO_SPACE_LEFT)) } runOnUiThread {
showRestartDialog(
this@MainActivity,
!e.message!!.contains(NO_SPACE_LEFT)
)
}
return@forEach 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.isFinished) {
if (workInfo.state == WorkInfo.State.FAILED) { if (workInfo.state == WorkInfo.State.FAILED) {
showRestartDialog(false) showRestartDialog(this)
} else if (workInfo.state == WorkInfo.State.SUCCEEDED) { } else if (workInfo.state == WorkInfo.State.SUCCEEDED) {
prefs[TAG_BUILD_VER] = versionName prefs[TAG_BUILD_VER] = versionName
startNative() startNative()
@ -229,17 +231,6 @@ class MainActivity : AppCompatActivity() {
viewModel.startOneTimeWorkRequest() 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 // connection dialog
private fun showConnectionDialog() { private fun showConnectionDialog() {
val builder = AlertDialog.Builder(this) val builder = AlertDialog.Builder(this)

View File

@ -26,9 +26,9 @@ import android.os.Build.VERSION_CODES.*
object ApiLevelHelper { object ApiLevelHelper {
private fun isGreaterOrEqual(versionCode: Int) = SDK_INT >= versionCode private fun isGreaterOrEqual(versionCode: Int) = SDK_INT >= versionCode
fun isLollipop() = isGreaterOrEqual(LOLLIPOP)
fun isMarshmallow() = isGreaterOrEqual(M) fun isMarshmallow() = isGreaterOrEqual(M)
fun isOreo() = isGreaterOrEqual(O) fun isOreo() = isGreaterOrEqual(O)
fun isAndroid12() = isGreaterOrEqual(S)
} }

View File

@ -20,11 +20,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
package com.multicraft.game.helpers package com.multicraft.game.helpers
import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.app.ActivityManager import android.app.ActivityManager
import android.app.AlarmManager import android.app.AlarmManager
import android.app.PendingIntent import android.app.PendingIntent
import android.app.PendingIntent.FLAG_CANCEL_CURRENT
import android.app.PendingIntent.FLAG_IMMUTABLE
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
@ -32,8 +33,8 @@ import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.BitmapDrawable
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
import android.view.View
import android.view.Window import android.view.Window
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.WindowCompat 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 androidx.core.view.WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
import com.multicraft.game.MainActivity import com.multicraft.game.MainActivity
import com.multicraft.game.R 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.isMarshmallow
import com.multicraft.game.helpers.ApiLevelHelper.isOreo import com.multicraft.game.helpers.ApiLevelHelper.isOreo
import com.multicraft.game.helpers.PreferenceHelper.TAG_SHORTCUT_EXIST import com.multicraft.game.helpers.PreferenceHelper.TAG_SHORTCUT_EXIST
import com.multicraft.game.helpers.PreferenceHelper.set import com.multicraft.game.helpers.PreferenceHelper.set
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
import kotlin.math.roundToInt
import kotlin.system.exitProcess import kotlin.system.exitProcess
object Utilities { 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 @JvmStatic
fun makeFullScreen(window: Window) { fun makeFullScreen(window: Window) {
if (isLollipop()) { WindowCompat.setDecorFitsSystemWindows(window, false)
WindowCompat.setDecorFitsSystemWindows(window, false) WindowInsetsControllerCompat(window, window.decorView).let {
WindowInsetsControllerCompat(window, window.decorView).let { it.hide(statusBars() or navigationBars())
it.hide(statusBars() or navigationBars()) it.systemBarsBehavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
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
} }
} }
@ -110,19 +94,39 @@ object Utilities {
@JvmStatic @JvmStatic
fun finishApp(restart: Boolean, activity: Activity) { fun finishApp(restart: Boolean, activity: Activity) {
if (restart) @SuppressLint("UnspecifiedImmutableFlag") { if (restart) {
val intent = Intent(activity, activity::class.java) val intent = Intent(activity, activity::class.java)
val mPendingIntentId = 1337 val mPendingIntentId = 1337
val flag = if (isAndroid12()) FLAG_IMMUTABLE else FLAG_CANCEL_CURRENT
val mgr = activity.getSystemService(Context.ALARM_SERVICE) as AlarmManager val mgr = activity.getSystemService(Context.ALARM_SERVICE) as AlarmManager
mgr.set( mgr.set(
AlarmManager.RTC, System.currentTimeMillis(), PendingIntent.getActivity( AlarmManager.RTC, System.currentTimeMillis(), PendingIntent.getActivity(
activity, mPendingIntentId, intent, PendingIntent.FLAG_CANCEL_CURRENT activity, mPendingIntentId, intent, flag
) )
) )
} }
exitProcess(0) 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) = fun File.copyInputStreamToFile(inputStream: InputStream) =
this.outputStream().use { fileOut -> inputStream.copyTo(fileOut, 8192) } this.outputStream().use { fileOut -> inputStream.copyTo(fileOut, 8192) }

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="?attr/colorBackgroundFloating" />
<corners android:radius="12dp" />
</shape>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/not_white" />
<corners android:radius="12dp" />
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -0,0 +1,86 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical"
android:paddingHorizontal="10dp"
android:paddingTop="10dp">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:gravity="center"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:text="@string/sorry"
android:textSize="22sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/restart"
android:paddingStart="12dp"
android:paddingTop="4dp"
android:paddingEnd="12dp"
android:paddingBottom="4dp"
android:src="@drawable/sad" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/error_desc"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:minHeight="128dp"
android:paddingStart="4dp"
android:paddingTop="4dp"
android:paddingEnd="4dp"
android:paddingBottom="4dp"
android:text="@string/restart"
android:textSize="15sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/close"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="8dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="4dp"
android:layout_marginBottom="4dp"
android:layout_weight="0.5"
android:text="@string/close_game"
android:textStyle="bold" />
<com.google.android.material.button.MaterialButton
android:id="@+id/restart"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="4dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
android:layout_weight="0.5"
android:text="@string/restart_game"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
</ScrollView>

View File

@ -6,8 +6,6 @@
<string name="loadingp">Загрузка&#8230; %d%%</string> <string name="loadingp">Загрузка&#8230; %d%%</string>
<string name="notification_title">Загрузка MultiCraft</string> <string name="notification_title">Загрузка MultiCraft</string>
<string name="notification_description">Осталось меньше минуты&#8230;</string> <string name="notification_description">Осталось меньше минуты&#8230;</string>
<string name="restart">Произошла ошибка, игра будет перезапущена автоматически</string>
<string name="no_space">Недостаточно места для записи файлов игры, пожалуйста освободите место в памяти</string>
<string name="input_text">Введите Текст</string> <string name="input_text">Введите Текст</string>
<string name="input_password">Пароль</string> <string name="input_password">Пароль</string>
@ -19,4 +17,10 @@
<string name="conn_mobile">3G/4G</string> <string name="conn_mobile">3G/4G</string>
<string name="ignore">Игнорировать</string> <string name="ignore">Игнорировать</string>
<!-- Crash -->
<string name="sorry">Нам очень жаль!</string>
<string name="restart">К сожалению, произошла непредвиденная ошибка, хотите перезапустить игру?</string>
<string name="no_space">Недостаточно места для записи файлов игры, пожалуйста освободите место на диске</string>
<string name="close_game">Закрыть игру</string>
<string name="restart_game">Перезапустить</string>
</resources> </resources>

View File

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="green">#008c80</color>
<color name="not_white">#fefefe</color> <color name="not_white">#fefefe</color>
</resources> </resources>

View File

@ -8,8 +8,6 @@
<string name="loadingp">Loading&#8230; %d%%</string> <string name="loadingp">Loading&#8230; %d%%</string>
<string name="notification_title">Loading MultiCraft</string> <string name="notification_title">Loading MultiCraft</string>
<string name="notification_description">Less than 1 minute&#8230;</string> <string name="notification_description">Less than 1 minute&#8230;</string>
<string name="restart">Unexpected issue, the game will be restarted automatically</string>
<string name="no_space">No space left for game files, please free space in the memory</string>
<string name="ok" translatable="false">OK</string> <string name="ok" translatable="false">OK</string>
<string name="input_text">Text Input</string> <string name="input_text">Text Input</string>
@ -23,4 +21,10 @@
<string name="conn_mobile">Mobile Data</string> <string name="conn_mobile">Mobile Data</string>
<string name="ignore">Ignore</string> <string name="ignore">Ignore</string>
<!-- Crash -->
<string name="sorry">We are sorry!</string>
<string name="restart">Unfortunately, unexpected issue was happened, would you like to restart the app?</string>
<string name="no_space">No space left for game files, please free up space on the disk before restarting the app</string>
<string name="close_game">Close game</string>
<string name="restart_game">Restart</string>
</resources> </resources>

View File

@ -7,6 +7,8 @@
<item name="android:windowFullscreen">true</item> <item name="android:windowFullscreen">true</item>
<item name="android:windowLayoutInDisplayCutoutMode" tools:targetApi="p">shortEdges</item> <item name="android:windowLayoutInDisplayCutoutMode" tools:targetApi="p">shortEdges</item>
<item name="fontFamily">@font/multicraftfont</item> <item name="fontFamily">@font/multicraftfont</item>
<item name="colorControlActivated">@color/green</item>
<item name="colorPrimary">@color/green</item>
</style> </style>
</resources> </resources>

View File

@ -15,9 +15,9 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { 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 '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 // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
} }

View File

@ -1,6 +1,6 @@
#Thu Nov 11 00:49:46 CET 2021 #Fri Feb 11 12:29:43 EET 2022
distributionBase=GRADLE_USER_HOME 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 distributionPath=wrapper/dists
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

View File

@ -2,12 +2,12 @@ apply plugin: 'com.android.library'
apply plugin: 'de.undercouch.download' apply plugin: 'de.undercouch.download'
android { android {
compileSdkVersion 31 compileSdkVersion 32
buildToolsVersion '31.0.0' buildToolsVersion '32.0.0'
ndkVersion '23.1.7779620' ndkVersion '23.1.7779620'
defaultConfig { defaultConfig {
minSdkVersion 19 minSdkVersion 21
targetSdkVersion 31 targetSdkVersion 32
externalNativeBuild { externalNativeBuild {
ndkBuild { ndkBuild {
arguments '-j' + Runtime.getRuntime().availableProcessors(), arguments '-j' + Runtime.getRuntime().availableProcessors(),

View File

@ -4474,10 +4474,16 @@ void the_game(bool *kill,
} catch (ServerError &e) { } catch (ServerError &e) {
error_message = e.what(); error_message = e.what();
errorstream << "ServerError: " << error_message << std::endl; errorstream << "ServerError: " << error_message << std::endl;
#ifdef __ANDROID__
porting::handleError("ServerError", error_message);
#endif
} catch (ModError &e) { } catch (ModError &e) {
error_message = std::string("ModError: ") + e.what() + error_message = std::string("ModError: ") + e.what() +
strgettext("\nCheck debug.txt for details."); strgettext("\nCheck debug.txt for details.");
errorstream << error_message << std::endl; errorstream << error_message << std::endl;
#ifdef __ANDROID__
porting::handleError("ModError", error_message);
#endif
} }
g_game = NULL; g_game = NULL;
game.shutdown(); game.shutdown();

View File

@ -56,7 +56,15 @@ void sanity_check_fn(const char *assertion, const char *file,
errorstream << file << ":" << line << ": " << function errorstream << file << ":" << line << ": " << function
<< ": An engine assumption '" << assertion << "' failed." << std::endl; << ": 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(); abort();
#endif
} }
void fatal_error_fn(const char *msg, const char *file, 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 errorstream << file << ":" << line << ": " << function
<< ": A fatal error occurred: " << msg << std::endl; << ": 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(); abort();
#endif
} }
#ifdef _MSC_VER #ifdef _MSC_VER

View File

@ -532,7 +532,7 @@ void set_default_settings()
// Set the optimal settings depending on the memory size [Android] | model [iOS] // Set the optimal settings depending on the memory size [Android] | model [iOS]
#ifdef __ANDROID__ #ifdef __ANDROID__
float memoryMax = porting::getMemoryMax(); float memoryMax = porting::getTotalSystemMemory();
#elif __IOS__ #elif __IOS__
float iOS_ver = [[[UIDevice currentDevice] systemVersion] floatValue]; float iOS_ver = [[[UIDevice currentDevice] systemVersion] floatValue];
#endif #endif

View File

@ -201,6 +201,9 @@ GUIEngine::GUIEngine(JoystickController *joystick,
} catch (LuaError &e) { } catch (LuaError &e) {
errorstream << "Main menu error: " << e.what() << std::endl; errorstream << "Main menu error: " << e.what() << std::endl;
m_data->script_data.errormessage = e.what(); m_data->script_data.errormessage = e.what();
#ifdef __ANDROID__
porting::handleError("Main menu error", e.what());
#endif
} }
m_menu->quitMenu(); m_menu->quitMenu();
@ -226,6 +229,9 @@ bool GUIEngine::loadMainMenuScript()
} catch (const ModError &e) { } catch (const ModError &e) {
errorstream << "GUIEngine: execution of menu script failed: " errorstream << "GUIEngine: execution of menu script failed: "
<< e.what() << std::endl; << e.what() << std::endl;
#ifdef __ANDROID__
porting::handleError("Main menu load error", e.what());
#endif
} }
return false; return false;

View File

@ -42,7 +42,6 @@ extern "C" void external_pause_game();
void android_main(android_app *app) void android_main(android_app *app)
{ {
int retval = 0;
porting::app_global = app; porting::app_global = app;
Thread::setName("Main"); Thread::setName("Main");
@ -53,15 +52,15 @@ void android_main(android_app *app)
free(argv[0]); free(argv[0]);
} catch (std::exception &e) { } catch (std::exception &e) {
errorstream << "Uncaught exception in main thread: " << e.what() << std::endl; errorstream << "Uncaught exception in main thread: " << e.what() << std::endl;
retval = -1; porting::finishGame(e.what());
} catch (...) { } catch (...) {
errorstream << "Uncaught exception in main thread!" << std::endl; errorstream << "Uncaught exception in main thread!" << std::endl;
retval = -1; porting::finishGame("Unknown error");
} }
porting::cleanupAndroid(); porting::cleanupAndroid();
infostream << "Shutting down." << std::endl; infostream << "Shutting down." << std::endl;
exit(retval); exit(0);
} }
/** /**
@ -95,8 +94,6 @@ android_app *app_global;
JNIEnv *jnienv; JNIEnv *jnienv;
jclass nativeActivity; jclass nativeActivity;
static float device_memory_max = 0;
jclass findClass(const std::string &classname) jclass findClass(const std::string &classname)
{ {
if (jnienv == nullptr) if (jnienv == nullptr)
@ -270,20 +267,12 @@ std::string getInputDialogValue()
return text; return text;
} }
float getMemoryMax() float getTotalSystemMemory()
{ {
if (device_memory_max == 0) { long pages = sysconf(_SC_PHYS_PAGES);
jmethodID getMemory = jnienv->GetMethodID(nativeActivity, long page_size = sysconf(_SC_PAGE_SIZE);
"getMemoryMax", "()F"); int divisor = 1024 * 1024 * 1024;
return pages * page_size / (float) divisor;
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;
} }
bool hasRealKeyboard() bool hasRealKeyboard()
@ -291,13 +280,26 @@ bool hasRealKeyboard()
return device_has_keyboard; 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) void notifyServerConnect(bool is_multiplayer)
{ {
jmethodID notifyConnect = jnienv->GetMethodID(nativeActivity, jmethodID notifyConnect = jnienv->GetMethodID(nativeActivity,
"notifyServerConnect", "(Z)V"); "notifyServerConnect", "(Z)V");
FATAL_ERROR_IF(notifyConnect == nullptr, 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; auto param = (jboolean) is_multiplayer;
@ -334,4 +336,59 @@ float getDisplayDensity()
return value; return value;
} }
#endif // ndef SERVER #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, "<init>", "([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);
}
} }

View File

@ -73,22 +73,41 @@ int getInputDialogState();
std::string getInputDialogValue(); std::string getInputDialogValue();
/** /**
* get max device RAM as integer value * get total device memory
* returns -1 on failure
*/ */
float getMemoryMax(); float getTotalSystemMemory();
/** /**
* notify java on server connection * notify java on server connection
*/ */
void notifyServerConnect(bool is_multiplayer); void notifyServerConnect(bool is_multiplayer);
/** /**
* notify java on game exit * notify java on game exit
*/ */
void notifyExitGame(); void notifyExitGame();
#ifndef SERVER #ifndef SERVER
float getDisplayDensity(); float getDisplayDensity();
#endif #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);
} }

View File

@ -480,6 +480,21 @@ int ModApiUtil::l_sha1(lua_State *L)
return 1; 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) void ModApiUtil::Initialize(lua_State *L, int top)
{ {
API_FCT(log); API_FCT(log);
@ -569,3 +584,8 @@ void ModApiUtil::InitializeAsync(lua_State *L, int top)
LuaSettings::create(L, g_settings, g_settings_path); LuaSettings::create(L, g_settings, g_settings_path);
lua_setfield(L, top, "settings"); lua_setfield(L, top, "settings");
} }
void ModApiUtil::InitializeMainMenu(lua_State *L, int top) {
Initialize(L, top);
API_FCT(upgrade);
}

View File

@ -101,10 +101,14 @@ private:
// sha1(string, raw) // sha1(string, raw)
static int l_sha1(lua_State *L); static int l_sha1(lua_State *L);
// upgrade(string)
static int l_upgrade(lua_State *L);
public: public:
static void Initialize(lua_State *L, int top); static void Initialize(lua_State *L, int top);
static void InitializeAsync(lua_State *L, int top); static void InitializeAsync(lua_State *L, int top);
static void InitializeClient(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); static void InitializeAsync(AsyncEngine &engine);
}; };

View File

@ -65,7 +65,7 @@ void MainMenuScripting::initializeModApi(lua_State *L, int top)
// Initialize mod API modules // Initialize mod API modules
ModApiMainMenu::Initialize(L, top); ModApiMainMenu::Initialize(L, top);
ModApiUtil::Initialize(L, top); ModApiUtil::InitializeMainMenu(L, top);
ModApiSound::Initialize(L, top); ModApiSound::Initialize(L, top);
ModApiHttp::Initialize(L, top); ModApiHttp::Initialize(L, top);