From 17ebe562a3a7c8c79a9fc7a4346c90bc642ba41a Mon Sep 17 00:00:00 2001 From: Maksim Date: Sun, 3 Jul 2022 21:32:55 +0300 Subject: [PATCH] Android: minor update --- build/android/app/build.gradle | 4 +- .../com/multicraft/game/GameActivity.java | 198 ---------------- .../java/com/multicraft/game/GameActivity.kt | 219 ++++++++++++++++++ .../java/com/multicraft/game/MainActivity.kt | 2 +- build/android/build.gradle | 2 +- build/android/native/build.gradle | 2 +- src/porting_android.cpp | 31 ++- 7 files changed, 247 insertions(+), 211 deletions(-) delete mode 100644 build/android/app/src/main/java/com/multicraft/game/GameActivity.java create mode 100644 build/android/app/src/main/java/com/multicraft/game/GameActivity.kt diff --git a/build/android/app/build.gradle b/build/android/app/build.gradle index 0b7895d54..7856a45c4 100644 --- a/build/android/app/build.gradle +++ b/build/android/app/build.gradle @@ -3,7 +3,7 @@ apply plugin: 'kotlin-android' android { compileSdkVersion 32 - buildToolsVersion '32.0.0' + buildToolsVersion '33.0.0' ndkVersion '23.2.8568313' defaultConfig { applicationId 'com.multicraft.game' @@ -136,7 +136,7 @@ dependencies { /* Third-party libraries */ implementation 'androidx.appcompat:appcompat:1.4.2' implementation 'androidx.appcompat:appcompat-resources:1.4.2' - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.0' implementation 'androidx.work:work-runtime-ktx:2.7.1' implementation 'com.google.android.material:material:1.6.1' } 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 deleted file mode 100644 index 848683fc4..000000000 --- a/build/android/app/src/main/java/com/multicraft/game/GameActivity.java +++ /dev/null @@ -1,198 +0,0 @@ -/* -MultiCraft -Copyright (C) 2014-2021 MoNTE48, Maksim Gamarnik -Copyright (C) 2014-2021 ubulem, Bektur Mambetov - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 3.0 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -package com.multicraft.game; - -import static android.content.res.Configuration.HARDKEYBOARDHIDDEN_NO; -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.makeFullScreen; - -import android.app.NativeActivity; -import android.content.Intent; -import android.content.res.Configuration; -import android.net.Uri; -import android.os.Bundle; -import android.view.KeyEvent; -import android.view.WindowManager.LayoutParams; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodManager; -import android.widget.Button; -import android.widget.EditText; - -import androidx.appcompat.app.AlertDialog; - -import com.multicraft.game.databinding.InputTextBinding; - -public class GameActivity extends NativeActivity { - public static boolean isMultiPlayer; - - static { - try { - System.loadLibrary("MultiCraft"); - } catch (UnsatisfiedLinkError e) { - System.exit(0); - } - } - - private int messageReturnCode = -1; - private String messageReturnValue = ""; - private boolean hasKeyboard; - - public static native void pauseGame(); - - public static native void keyboardEvent(boolean keyboard); - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().addFlags(LayoutParams.FLAG_KEEP_SCREEN_ON); - hasKeyboard = getResources().getConfiguration().hardKeyboardHidden == HARDKEYBOARDHIDDEN_NO; - keyboardEvent(hasKeyboard); - } - - @Override - public void onWindowFocusChanged(boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); - if (hasFocus) makeFullScreen(getWindow()); - } - - @Override - public void onBackPressed() { - // Ignore the back press so MultiCraft can handle it - } - - @Override - protected void onPause() { - super.onPause(); - pauseGame(); - } - - @Override - protected void onResume() { - super.onResume(); - makeFullScreen(getWindow()); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - boolean statusKeyboard = getResources().getConfiguration().hardKeyboardHidden == HARDKEYBOARDHIDDEN_NO; - if (hasKeyboard != statusKeyboard) { - hasKeyboard = statusKeyboard; - keyboardEvent(hasKeyboard); - } - } - - public void showDialog(String s, String hint, String current, int editType) { - runOnUiThread(() -> showDialogUI(hint, current, editType)); - } - - private void showDialogUI(String hint, String current, int editType) { - final AlertDialog.Builder builder = new AlertDialog.Builder(this); - if (editType == 1) builder.setPositiveButton(R.string.done, null); - InputTextBinding binding = InputTextBinding.inflate(getLayoutInflater()); - String hintText = (!hint.isEmpty()) ? hint : getResources().getString( - (editType == 3) ? R.string.input_password : R.string.input_text); - binding.inputLayout.setHint(hintText); - builder.setView(binding.getRoot()); - AlertDialog alertDialog = builder.create(); - EditText editText = binding.editText; - editText.requestFocus(); - editText.setText(current); - if (editType != 1) editText.setImeOptions(EditorInfo.IME_FLAG_NO_FULLSCREEN); - final InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); - int inputType = TYPE_CLASS_TEXT; - if (editType == 1) { - inputType = inputType | TYPE_TEXT_FLAG_MULTI_LINE; - editText.setMaxLines(8); - } else if (editType == 3) - inputType = inputType | TYPE_TEXT_VARIATION_PASSWORD; - editText.setInputType(inputType); - editText.setSelection(editText.getText().length()); - editText.setOnEditorActionListener((view, KeyCode, event) -> { - if (KeyCode == KeyEvent.KEYCODE_ENTER || KeyCode == KeyEvent.KEYCODE_ENDCALL) { - imm.hideSoftInputFromWindow(editText.getWindowToken(), 0); - messageReturnCode = 0; - messageReturnValue = editText.getText().toString(); - alertDialog.dismiss(); - return true; - } - return false; - }); - // should be above `show()` - alertDialog.getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_VISIBLE); - alertDialog.show(); - Button button = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE); - if (button != null) { - button.setOnClickListener(view -> { - imm.hideSoftInputFromWindow(editText.getWindowToken(), 0); - messageReturnCode = 0; - messageReturnValue = editText.getText().toString(); - alertDialog.dismiss(); - }); - } - alertDialog.setOnCancelListener(dialog -> { - getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); - messageReturnValue = current; - messageReturnCode = -1; - }); - } - - public int getDialogState() { - return messageReturnCode; - } - - public String getDialogValue() { - messageReturnCode = -1; - return messageReturnValue; - } - - public float getDensity() { - return getResources().getDisplayMetrics().density; - } - - public void notifyServerConnect(boolean multiplayer) { - isMultiPlayer = multiplayer; - } - - public void notifyExitGame() { - } - - public void openURI(String uri) { - try { - Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri)); - startActivity(browserIntent); - } catch (Exception ignored) { - } - } - - public void finishGame(String exc) { - finishApp(true, this); - } - - public void handleError(String exc) { - } - - public void upgrade(String item) { - } -} diff --git a/build/android/app/src/main/java/com/multicraft/game/GameActivity.kt b/build/android/app/src/main/java/com/multicraft/game/GameActivity.kt new file mode 100644 index 000000000..cbdad7c42 --- /dev/null +++ b/build/android/app/src/main/java/com/multicraft/game/GameActivity.kt @@ -0,0 +1,219 @@ +/* +MultiCraft +Copyright (C) 2014-2022 MoNTE48, Maksim Gamarnik +Copyright (C) 2014-2022 ubulem, Bektur Mambetov + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 3.0 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +package com.multicraft.game + +import android.app.NativeActivity +import android.content.Intent +import android.content.res.Configuration +import android.content.res.Configuration.HARDKEYBOARDHIDDEN_NO +import android.net.Uri +import android.os.Bundle +import android.text.InputType +import android.view.* +import android.view.inputmethod.EditorInfo +import android.view.inputmethod.InputMethodManager +import android.widget.TextView +import androidx.appcompat.app.AlertDialog +import com.multicraft.game.databinding.InputTextBinding +import com.multicraft.game.helpers.Utilities.finishApp +import com.multicraft.game.helpers.Utilities.makeFullScreen +import kotlin.system.exitProcess + +class GameActivity : NativeActivity() { + companion object { + var isMultiPlayer = false + var isInputActive = false + + @JvmStatic + external fun pauseGame() + + @JvmStatic + external fun keyboardEvent(keyboard: Boolean) + + init { + try { + System.loadLibrary("MultiCraft") + } catch (e: UnsatisfiedLinkError) { + exitProcess(0) + } + } + } + + private var messageReturnCode = -1 + private var messageReturnValue = "" + private var hasKeyboard = false + + public override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + hasKeyboard = resources.configuration.hardKeyboardHidden == HARDKEYBOARDHIDDEN_NO + keyboardEvent(hasKeyboard) + } + + override fun onWindowFocusChanged(hasFocus: Boolean) { + super.onWindowFocusChanged(hasFocus) + if (hasFocus) makeFullScreen(window) + } + + override fun onBackPressed() { + // Ignore the back press so MultiCraft can handle it + } + + override fun onPause() { + super.onPause() + pauseGame() + } + + override fun onResume() { + super.onResume() + makeFullScreen(window) + } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + val statusKeyboard = + resources.configuration.hardKeyboardHidden == HARDKEYBOARDHIDDEN_NO + if (hasKeyboard != statusKeyboard) { + hasKeyboard = statusKeyboard + keyboardEvent(hasKeyboard) + } + } + + @Suppress("unused") + fun showDialog( + @Suppress("UNUSED_PARAMETER") s: String?, + hint: String?, current: String?, editType: Int + ) { + runOnUiThread { showDialogUI(hint, current, editType) } + } + + private fun showDialogUI(hint: String?, current: String?, editType: Int) { + isInputActive = true + val builder = AlertDialog.Builder(this) + if (editType == 1) builder.setPositiveButton(R.string.done, null) + val binding = InputTextBinding.inflate(layoutInflater) + val hintText = hint?.ifEmpty { + resources.getString(if (editType == 3) R.string.input_password else R.string.input_text) + } + binding.inputLayout.hint = hintText + builder.setView(binding.root) + val alertDialog = builder.create() + val editText = binding.editText + editText.requestFocus() + editText.setText(current.toString()) + if (editType != 1) editText.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN + val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager + var inputType = InputType.TYPE_CLASS_TEXT + when (editType) { + 1 -> { + inputType = inputType or InputType.TYPE_TEXT_FLAG_MULTI_LINE + editText.maxLines = 8 + } + 3 -> inputType = inputType or InputType.TYPE_TEXT_VARIATION_PASSWORD + } + editText.inputType = inputType + editText.setSelection(editText.text?.length ?: 0) + // for Android OS + editText.setOnEditorActionListener { _: TextView?, KeyCode: Int, _: KeyEvent? -> + if (KeyCode == KeyEvent.KEYCODE_ENTER || KeyCode == KeyEvent.KEYCODE_ENDCALL) { + imm.hideSoftInputFromWindow(editText.windowToken, 0) + messageReturnCode = 0 + messageReturnValue = editText.text.toString() + alertDialog.dismiss() + isInputActive = false + return@setOnEditorActionListener true + } + return@setOnEditorActionListener false + } + // for Chrome OS + editText.setOnKeyListener { _: View?, KeyCode: Int, _: KeyEvent? -> + if (KeyCode == KeyEvent.KEYCODE_ENTER || KeyCode == KeyEvent.KEYCODE_ENDCALL) { + imm.hideSoftInputFromWindow(editText.windowToken, 0) + messageReturnCode = 0 + messageReturnValue = editText.text.toString() + alertDialog.dismiss() + isInputActive = false + return@setOnKeyListener true + } + return@setOnKeyListener false + } + // should be above `show()` + alertDialog.window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE) + alertDialog.show() + val button = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE) + button?.setOnClickListener { + imm.hideSoftInputFromWindow(editText.windowToken, 0) + messageReturnCode = 0 + messageReturnValue = editText.text.toString() + alertDialog.dismiss() + isInputActive = false + } + alertDialog.setOnCancelListener { + window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN) + messageReturnValue = current.toString() + messageReturnCode = -1 + isInputActive = false + } + } + + @Suppress("unused") + fun getDialogState() = messageReturnCode + + @Suppress("unused") + fun getDialogValue(): String { + messageReturnCode = -1 + return messageReturnValue + } + + @Suppress("unused") + fun getDensity() = resources.displayMetrics.density + + @Suppress("unused") + fun notifyServerConnect(multiplayer: Boolean) { + isMultiPlayer = multiplayer + } + + @Suppress("unused") + fun notifyExitGame() { + } + + @Suppress("unused") + fun openURI(uri: String?) { + try { + val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(uri)) + startActivity(browserIntent) + } catch (ignored: Exception) { + } + } + + @Suppress("unused") + fun finishGame(exc: String?) { + finishApp(true, this) + } + + @Suppress("unused") + fun handleError(exc: String?) { + } + + @Suppress("unused") + fun upgrade(item: String) { + } +} 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 c2cc357d4..ac247aedc 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 @@ -81,12 +81,12 @@ class MainActivity : AppCompatActivity() { } } + @Deprecated("Deprecated in Java") override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { @Suppress("DEPRECATION") super.onActivityResult(requestCode, resultCode, data) if (requestCode == REQUEST_CONNECTION) checkAppVersion() - } override fun onBackPressed() { diff --git a/build/android/build.gradle b/build/android/build.gradle index c06b87608..8cfb99704 100644 --- a/build/android/build.gradle +++ b/build/android/build.gradle @@ -18,7 +18,7 @@ buildscript { classpath 'com.android.tools.build:gradle:7.2.1' //noinspection GradleDependency classpath 'de.undercouch:gradle-download-task:4.1.2' - classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21' + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } diff --git a/build/android/native/build.gradle b/build/android/native/build.gradle index 8aaa36a7b..a5346be99 100644 --- a/build/android/native/build.gradle +++ b/build/android/native/build.gradle @@ -3,7 +3,7 @@ apply plugin: 'de.undercouch.download' android { compileSdkVersion 32 - buildToolsVersion '32.0.0' + buildToolsVersion '33.0.0' ndkVersion '23.2.8568313' defaultConfig { minSdkVersion 21 diff --git a/src/porting_android.cpp b/src/porting_android.cpp index 59b4cbc8c..b838fcf9f 100644 --- a/src/porting_android.cpp +++ b/src/porting_android.cpp @@ -44,8 +44,6 @@ extern "C" void external_pause_game(); static std::atomic ran = {false}; -jmethodID notifyExit; - void android_main(android_app *app) { if (ran.exchange(true)) { @@ -105,6 +103,12 @@ jclass findClass(const std::string &classname) return nullptr; jclass nativeactivity = jnienv->FindClass("android/app/NativeActivity"); + + if (jnienv->ExceptionCheck()) { + jnienv->ExceptionClear(); + return nullptr; + } + jmethodID getClassLoader = jnienv->GetMethodID( nativeactivity, "getClassLoader", "()Ljava/lang/ClassLoader;"); jobject cls = jnienv->CallObjectMethod(activityObj, getClassLoader); @@ -112,7 +116,14 @@ jclass findClass(const std::string &classname) jmethodID findClass = jnienv->GetMethodID(classLoader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); jstring strClassName = jnienv->NewStringUTF(classname.c_str()); - return (jclass) jnienv->CallObjectMethod(cls, findClass, strClassName); + jclass result = (jclass) jnienv->CallObjectMethod(cls, findClass, strClassName); + + if (jnienv->ExceptionCheck()) { + jnienv->ExceptionClear(); + return nullptr; + } + + return result; } void initAndroid() @@ -136,10 +147,6 @@ void initAndroid() "porting::initAndroid unable to find java native activity class" << std::endl; - notifyExit = jnienv->GetMethodID(nativeActivity, "notifyExitGame", "()V"); - FATAL_ERROR_IF(notifyExit == nullptr, - "porting::initAndroid unable to find java notifyExit method"); - #ifdef GPROF // in the start-up code __android_log_print(ANDROID_LOG_ERROR, PROJECT_NAME_C, @@ -317,9 +324,17 @@ void notifyServerConnect(bool is_multiplayer) void notifyExitGame() { - if (jnienv == nullptr || activityObj == nullptr || notifyExit == nullptr) + if (jnienv == nullptr || activityObj == nullptr) return; + jclass _nativeActivity = findClass("com/multicraft/game/GameActivity"); + FATAL_ERROR_IF(_nativeActivity == nullptr, + "porting::notifyExitGame unable to find java native activity class"); + + jmethodID notifyExit = jnienv->GetMethodID(_nativeActivity, "notifyExitGame", "()V"); + FATAL_ERROR_IF(notifyExit == nullptr, + "porting::notifyExitGame unable to find java notifyExit method"); + jnienv->CallVoidMethod(activityObj, notifyExit); if (jnienv->ExceptionOccurred())