diff --git a/platform/android/java/editor/src/main/java/net/relintai/pandemonium/editor/PandemoniumEditor.kt b/platform/android/java/editor/src/main/java/net/relintai/pandemonium/editor/PandemoniumEditor.kt index a5098075c..462158e97 100644 --- a/platform/android/java/editor/src/main/java/net/relintai/pandemonium/editor/PandemoniumEditor.kt +++ b/platform/android/java/editor/src/main/java/net/relintai/pandemonium/editor/PandemoniumEditor.kt @@ -35,12 +35,10 @@ import net.relintai.pandemonium.pandemonium.utils.PermissionsUtil import net.relintai.pandemonium.pandemonium.utils.ProcessPhoenix import android.Manifest +import android.app.ActivityManager +import android.content.Context import android.content.Intent import android.content.pm.PackageManager -import android.os.Build -import android.os.Bundle -import android.os.Debug -import android.os.Environment import android.util.Log import android.widget.Toast @@ -65,11 +63,18 @@ open class PandemoniumEditor : FullScreenPandemoniumApp() { private const val WAIT_FOR_DEBUGGER = false private const val COMMAND_LINE_PARAMS = "command_line_params" + private const val EDITOR_ID = 777 private const val EDITOR_ARG = "--editor" private const val EDITOR_ARG_SHORT = "-e" + private const val EDITOR_PROCESS_NAME_SUFFIX = ":PandemoniumEditor" + private const val GAME_ID = 667 + private const val GAME_PROCESS_NAME_SUFFIX = ":PandemoniumGame" + + private const val PROJECT_MANAGER_ID = 555 private const val PROJECT_MANAGER_ARG = "--project-manager" private const val PROJECT_MANAGER_ARG_SHORT = "-p" + private const val PROJECT_MANAGER_PROCESS_NAME_SUFFIX = ":PandemoniumProjectManager" } private val commandLineParams = ArrayList() @@ -104,9 +109,10 @@ open class PandemoniumEditor : FullScreenPandemoniumApp() { override fun getCommandLine() = commandLineParams - override fun onNewPandemoniumInstanceRequested(args: Array) { + override fun onNewPandemoniumInstanceRequested(args: Array): Int { // Parse the arguments to figure out which activity to start. var targetClass: Class<*> = PandemoniumGame::class.java + var instanceId = GAME_ID // Whether we should launch the new godot instance in an adjacent window // https://developer.android.com/reference/android/content/Intent#FLAG_ACTIVITY_LAUNCH_ADJACENT @@ -116,12 +122,14 @@ open class PandemoniumEditor : FullScreenPandemoniumApp() { if (EDITOR_ARG == arg || EDITOR_ARG_SHORT == arg) { targetClass = PandemoniumEditor::class.java launchAdjacent = false + instanceId = EDITOR_ID break } if (PROJECT_MANAGER_ARG == arg || PROJECT_MANAGER_ARG_SHORT == arg) { targetClass = PandemoniumProjectManager::class.java launchAdjacent = false + instanceId = PROJECT_MANAGER_ID break } } @@ -140,6 +148,39 @@ open class PandemoniumEditor : FullScreenPandemoniumApp() { Log.d(TAG, "Starting $targetClass") startActivity(newInstance) } + + return instanceId + } + + override fun onPandemoniumForceQuit(pandemoniumInstanceId: Int): Boolean { + val processNameSuffix = when (pandemoniumInstanceId) { + GAME_ID -> { + GAME_PROCESS_NAME_SUFFIX + } + EDITOR_ID -> { + EDITOR_PROCESS_NAME_SUFFIX + } + PROJECT_MANAGER_ID -> { + PROJECT_MANAGER_PROCESS_NAME_SUFFIX + } + else -> "" + } + + if (processNameSuffix.isBlank()) { + return false + } + + val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + val runningProcesses = activityManager.runningAppProcesses + for (runningProcess in runningProcesses) { + if (runningProcess.processName.endsWith(processNameSuffix)) { + Log.v(TAG, "Killing Pandemonium process ${runningProcess.processName}") + Process.killProcess(runningProcess.pid) + return true + } + } + + return false } // Get the screen's density scale diff --git a/platform/android/java/lib/src/net/relintai/pandemonium/pandemonium/FullScreenPandemoniumApp.java b/platform/android/java/lib/src/net/relintai/pandemonium/pandemonium/FullScreenPandemoniumApp.java index b4a174046..521157a29 100644 --- a/platform/android/java/lib/src/net/relintai/pandemonium/pandemonium/FullScreenPandemoniumApp.java +++ b/platform/android/java/lib/src/net/relintai/pandemonium/pandemonium/FullScreenPandemoniumApp.java @@ -74,28 +74,36 @@ public abstract class FullScreenPandemoniumApp extends FragmentActivity implemen public void onDestroy() { Log.v(TAG, "Destroying Pandemonium app..."); super.onDestroy(); - onPandemoniumForceQuit(pandemoniumFragment); + terminatePandemoniumInstance(pandemoniumFragment); } @Override public final void onPandemoniumForceQuit(Pandemonium instance) { + runOnUiThread(() -> { + terminatePandemoniumInstance(instance); + }); + } + + private void terminatePandemoniumInstance(Pandemonium instance) { if (instance == pandemoniumFragment) { Log.v(TAG, "Force quitting Pandemonium instance"); - ProcessPhoenix.forceQuit(this); + ProcessPhoenix.forceQuit(FullScreenPandemoniumApp.this); } } @Override public final void onPandemoniumRestartRequested(Pandemonium instance) { - if (instance == pandemoniumFragment) { - // It's very hard to properly de-initialize Pandemonium on Android to restart the game - // from scratch. Therefore, we need to kill the whole app process and relaunch it. - // - // Restarting only the activity, wouldn't be enough unless it did proper cleanup (including - // releasing and reloading native libs or resetting their state somehow and clearing statics). - Log.v(TAG, "Restarting Pandemonium instance..."); - ProcessPhoenix.triggerRebirth(this); - } + runOnUiThread(() -> { + if (instance == pandemoniumFragment) { + // It's very hard to properly de-initialize Pandemonium on Android to restart the game + // from scratch. Therefore, we need to kill the whole app process and relaunch it. + // + // Restarting only the activity, wouldn't be enough unless it did proper cleanup (including + // releasing and reloading native libs or resetting their state somehow and clearing statics). + Log.v(TAG, "Restarting Pandemonium instance..."); + ProcessPhoenix.triggerRebirth(FullScreenPandemoniumApp.this); + } + }); } @Override diff --git a/platform/android/java/lib/src/net/relintai/pandemonium/pandemonium/Pandemonium.java b/platform/android/java/lib/src/net/relintai/pandemonium/pandemonium/Pandemonium.java index 3e93c9128..34ad63376 100644 --- a/platform/android/java/lib/src/net/relintai/pandemonium/pandemonium/Pandemonium.java +++ b/platform/android/java/lib/src/net/relintai/pandemonium/pandemonium/Pandemonium.java @@ -467,11 +467,9 @@ public class Pandemonium extends Fragment implements SensorEventListener, IDownl } public void restart() { - runOnUiThread(() -> { - if (pandemoniumHost != null) { - pandemoniumHost.onPandemoniumRestartRequested(this); - } - }); + if (pandemoniumHost != null) { + pandemoniumHost.onPandemoniumRestartRequested(this); + } } public void alert(final String message, final String title) { @@ -1029,11 +1027,20 @@ public class Pandemonium extends Fragment implements SensorEventListener, IDownl private void forceQuit() { // TODO: This is a temp solution. The proper fix will involve tracking down and properly shutting down each // native Pandemonium components that is started in Pandemonium#onVideoInit. - runOnUiThread(() -> { - if (pandemoniumHost != null) { - pandemoniumHost.onPandemoniumForceQuit(this); - } - }); + forceQuit(0); + } + + @Keep + private boolean forceQuit(int instanceId) { + if (pandemoniumHost == null) { + return false; + } + if (instanceId == 0) { + pandemoniumHost.onPandemoniumForceQuit(this); + return true; + } else { + return pandemoniumHost.onPandemoniumForceQuit(instanceId); + } } private boolean obbIsCorrupted(String f, String main_pack_md5) { @@ -1189,10 +1196,10 @@ public class Pandemonium extends Fragment implements SensorEventListener, IDownl @Keep private void createNewPandemoniumInstance(String[] args) { - runOnUiThread(() -> { - if (pandemoniumHost != null) { - pandemoniumHost.onNewPandemoniumInstanceRequested(args); - } - }); + if (pandemoniumHost != null) { + pandemoniumHost.onNewPandemoniumInstanceRequested(args); + } + + return 0; } } diff --git a/platform/android/java/lib/src/net/relintai/pandemonium/pandemonium/PandemoniumHost.java b/platform/android/java/lib/src/net/relintai/pandemonium/pandemonium/PandemoniumHost.java index 8e4b99a18..edd8495b7 100644 --- a/platform/android/java/lib/src/net/relintai/pandemonium/pandemonium/PandemoniumHost.java +++ b/platform/android/java/lib/src/net/relintai/pandemonium/pandemonium/PandemoniumHost.java @@ -55,21 +55,35 @@ public interface PandemoniumHost { default void onPandemoniumMainLoopStarted() {} /** - * Invoked on the UI thread as the last step of the Pandemonium instance clean up phase. + * Invoked on the render thread to terminate the given Pandemonium instance. */ default void onPandemoniumForceQuit(Pandemonium instance) {} /** - * Invoked on the UI thread when the Pandemonium instance wants to be restarted. It's up to the host + * Invoked on the render thread to terminate the Pandemonium instance with the given id. + * @param pandemoniumInstanceId id of the Pandemonium instance to terminate. See {@code onNewPandemoniumInstanceRequested} + * + * @return true if successful, false otherwise. + */ + default boolean onPandemoniumForceQuit(int pandemoniumInstanceId) { + return false; + } + + /** + * Invoked on the render thread when the Pandemonium instance wants to be restarted. It's up to the host * to perform the appropriate action(s). */ default void onPandemoniumRestartRequested(Pandemonium instance) {} /** - * Invoked on the UI thread when a new Pandemonium instance is requested. It's up to the host to + * Invoked on the render thread when a new Pandemonium instance is requested. It's up to the host to * perform the appropriate action(s). * * @param args Arguments used to initialize the new instance. + * + * @return the id of the new instance. See {@code onPandemoniumForceQuit} */ - default void onNewPandemoniumInstanceRequested(String[] args) {} + default void onNewPandemoniumInstanceRequested(String[] args) { + return 0; + } } diff --git a/platform/android/java_pandemonium_wrapper.cpp b/platform/android/java_pandemonium_wrapper.cpp index 26833f77c..273a1bbf9 100644 --- a/platform/android/java_pandemonium_wrapper.cpp +++ b/platform/android/java_pandemonium_wrapper.cpp @@ -63,7 +63,7 @@ PandemoniumJavaWrapper::PandemoniumJavaWrapper(JNIEnv *p_env, jobject p_activity _destroy_offscreen_gl = p_env->GetMethodID(pandemonium_class, "destroyOffscreenGL", "()V"); _set_offscreen_gl_current = p_env->GetMethodID(pandemonium_class, "setOffscreenGLCurrent", "(Z)V"); _restart = p_env->GetMethodID(pandemonium_class, "restart", "()V"); - _finish = p_env->GetMethodID(pandemonium_class, "forceQuit", "()V"); + _finish = p_env->GetMethodID(pandemonium_class, "forceQuit", "(I)Z"); _set_keep_screen_on = p_env->GetMethodID(pandemonium_class, "setKeepScreenOn", "(Z)V"); _alert = p_env->GetMethodID(pandemonium_class, "alert", "(Ljava/lang/String;Ljava/lang/String;)V"); _get_GLES_version_code = p_env->GetMethodID(pandemonium_class, "getGLESVersionCode", "()I"); @@ -80,7 +80,7 @@ PandemoniumJavaWrapper::PandemoniumJavaWrapper(JNIEnv *p_env, jobject p_activity _get_input_fallback_mapping = p_env->GetMethodID(pandemonium_class, "getInputFallbackMapping", "()Ljava/lang/String;"); _on_pandemonium_setup_completed = p_env->GetMethodID(pandemonium_class, "onPandemoniumSetupCompleted", "()V"); _on_pandemonium_main_loop_started = p_env->GetMethodID(pandemonium_class, "onPandemoniumMainLoopStarted", "()V"); - _create_new_pandemonium_instance = p_env->GetMethodID(pandemonium_class, "createNewPandemoniumInstance", "([Ljava/lang/String;)V"); + _create_new_pandemonium_instance = p_env->GetMethodID(pandemonium_class, "createNewPandemoniumInstance", "([Ljava/lang/String;)I"); _request_framebuffer_swap = p_env->GetMethodID(pandemonium_class, "requestFramebufferSwap", "()V"); _get_render_view = p_env->GetMethodID(pandemonium_class, "getRenderView", "()Lnet/relintai/pandemonium/pandemonium/PandemoniumView;"); @@ -215,13 +215,15 @@ void PandemoniumJavaWrapper::restart(JNIEnv *p_env) { } } -void PandemoniumJavaWrapper::force_quit(JNIEnv *p_env) { +bool PandemoniumJavaWrapper::force_quit(JNIEnv *p_env, int p_instance_id) { if (_finish) { - if (p_env == NULL) + if (p_env == NULL) { p_env = get_jni_env(); - ERR_FAIL_COND(p_env == nullptr); + } - p_env->CallVoidMethod(pandemonium_instance, _finish); + ERR_FAIL_NULL_V(p_env, false); + + return p_env->CallBooleanMethod(pandemonium_instance, _finish, p_instance_id); } } @@ -397,16 +399,20 @@ void PandemoniumJavaWrapper::vibrate(int p_duration_ms) { } } -void PandemoniumJavaWrapper::create_new_pandemonium_instance(List args) { +int PandemoniumJavaWrapper::create_new_pandemonium_instance(List args) { if (_create_new_pandemonium_instance) { JNIEnv *env = get_jni_env(); - ERR_FAIL_COND(env == nullptr); + + ERR_FAIL_NULL_V(env, 0); jobjectArray jargs = env->NewObjectArray(args.size(), env->FindClass("java/lang/String"), env->NewStringUTF("")); for (int i = 0; i < args.size(); i++) { env->SetObjectArrayElement(jargs, i, env->NewStringUTF(args[i].utf8().get_data())); } - env->CallVoidMethod(pandemonium_instance, _create_new_pandemonium_instance, jargs); + + return env->CallIntMethod(pandemonium_instance, _create_new_pandemonium_instance, jargs); + } else { + return 0; } } diff --git a/platform/android/java_pandemonium_wrapper.h b/platform/android/java_pandemonium_wrapper.h index bba9110cd..ebd3276e4 100644 --- a/platform/android/java_pandemonium_wrapper.h +++ b/platform/android/java_pandemonium_wrapper.h @@ -96,7 +96,7 @@ public: void on_pandemonium_setup_completed(JNIEnv *p_env = NULL); void on_pandemonium_main_loop_started(JNIEnv *p_env = NULL); void restart(JNIEnv *p_env = NULL); - void force_quit(JNIEnv *p_env = NULL); + bool force_quit(JNIEnv *p_env = NULL, int p_instance_id = 0); void set_keep_screen_on(bool p_enabled); void alert(const String &p_message, const String &p_title); int get_gles_version_code(); @@ -114,7 +114,7 @@ public: bool is_activity_resumed(); void vibrate(int p_duration_ms); String get_input_fallback_mapping(); - void create_new_pandemonium_instance(List args); + int create_new_pandemonium_instance(List args); void request_framebuffer_swap(); }; diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index 314ea58e0..75e58cd06 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -692,9 +692,22 @@ void OS_Android::swap_buffers() { } Error OS_Android::create_instance(const List &p_arguments, ProcessID *r_child_id) { - pandemonium_java->create_new_pandemonium_instance(p_arguments); + int instance_id = pandemonium_java->create_new_pandemonium_instance(p_arguments); + + if (r_child_id) { + *r_child_id = instance_id; + } + return OK; } +Error OS_Android::kill(const ProcessID &p_pid) { + if (pandemonium_java->force_quit(NULL, p_pid)) { + return OK; + } + + return OS_Unix::kill(p_pid); +} + OS_Android::~OS_Android() { } diff --git a/platform/android/os_android.h b/platform/android/os_android.h index 01f4df4c1..534592761 100644 --- a/platform/android/os_android.h +++ b/platform/android/os_android.h @@ -210,6 +210,7 @@ public: virtual String get_config_path() const; virtual Error execute(const String &p_path, const List &p_arguments, bool p_blocking = true, ProcessID *r_child_id = nullptr, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false); + virtual Error kill(const ProcessID &p_pid); void swap_buffers();