From dfd80f3d762441e34e501c18a20b50c09fe3730f Mon Sep 17 00:00:00 2001
From: Sylvain <[EMAIL REDACTED]>
Date: Wed, 5 Apr 2023 21:54:36 +0200
Subject: [PATCH] Android: control activity re-creation
---
WhatsNew.txt | 1 +
.../main/java/org/libsdl/app/SDLActivity.java | 22 ++++++++++++++
docs/README-android.md | 7 +++++
include/SDL3/SDL_hints.h | 13 +++++++++
src/core/android/SDL_android.c | 29 ++++++++++++++++++-
5 files changed, 71 insertions(+), 1 deletion(-)
diff --git a/WhatsNew.txt b/WhatsNew.txt
index ea88419aa0f3..1e91ba8cb71a 100644
--- a/WhatsNew.txt
+++ b/WhatsNew.txt
@@ -29,3 +29,4 @@ General:
* Added SDL_GetRenderVSync() to get vsync of the given renderer
* Added SDL_PlayAudioDevice() to start audio playback
* Added SDL_ConvertAudioSamples() to convert audio samples from one format to another
+* Added SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY hint to control re-creation of Android SDL activity.
diff --git a/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java b/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java
index de03ba790b28..db524b639c76 100644
--- a/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java
+++ b/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java
@@ -221,6 +221,8 @@ public enum NativeState {
// This is what SDL runs in. It invokes SDL_main(), eventually
protected static Thread mSDLThread;
+ protected static boolean mSDLMainFinished = false;
+ protected static boolean mActivityCreated = false;
protected static SDLGenericMotionListener_API12 getMotionListener() {
if (mMotionListener == null) {
@@ -324,6 +326,24 @@ protected void onCreate(Bundle savedInstanceState) {
Log.v(TAG, "onCreate()");
super.onCreate(savedInstanceState);
+
+ /* Control activity re-creation */
+ if (mSDLMainFinished || mActivityCreated) {
+ boolean allow_recreate = SDLActivity.nativeAllowRecreateActivity();
+ if (mSDLMainFinished) {
+ Log.v(TAG, "SDL main() finished");
+ }
+ if (allow_recreate) {
+ Log.v(TAG, "activity re-created");
+ } else {
+ Log.v(TAG, "activity finished");
+ System.exit(0);
+ return;
+ }
+ }
+
+ mActivityCreated = true;
+
try {
Thread.currentThread().setName("SDLActivity");
} catch (Exception e) {
@@ -950,6 +970,7 @@ public static native void onNativeTouch(int touchDevId, int pointerFingerId,
public static native void nativePermissionResult(int requestCode, boolean result);
public static native void onNativeLocaleChanged();
public static native void onNativeDarkModeChanged(boolean enabled);
+ public static native boolean nativeAllowRecreateActivity();
/**
* This method is called by SDL using JNI.
@@ -1902,6 +1923,7 @@ public void run() {
if (SDLActivity.mSingleton != null && !SDLActivity.mSingleton.isFinishing()) {
// Let's finish the Activity
SDLActivity.mSDLThread = null;
+ SDLActivity.mSDLMainFinished = true;
SDLActivity.mSingleton.finish();
} // else: Activity is already being destroyed
diff --git a/docs/README-android.md b/docs/README-android.md
index 4f6b75a0aad1..0c21ce8f14c9 100644
--- a/docs/README-android.md
+++ b/docs/README-android.md
@@ -218,6 +218,13 @@ You should not use the SDL renderer API while the app going in background:
GL context is restored, and the SDL renderer API is available (unless you
receive SDL_EVENT_RENDER_DEVICE_RESET).
+Activity lifecyle
+================================================================================
+
+You can control activity re-creation (eg. onCreate()) behaviour. This allows to keep
+or re-initialize java and native static datas, see SDL_hints.h:
+- SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY
+
Mouse / Touch events
================================================================================
diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h
index 70f95957bed4..09830b563c86 100644
--- a/include/SDL3/SDL_hints.h
+++ b/include/SDL3/SDL_hints.h
@@ -145,6 +145,19 @@ extern "C" {
*/
#define SDL_HINT_ANDROID_TRAP_BACK_BUTTON "SDL_ANDROID_TRAP_BACK_BUTTON"
+/**
+ * \brief A variable to control whether SDL activity is allowed to be re-created.
+ * If so, java static datas and static datas from native libraries remain with their current values.
+ * When not allowed, the activity terminates with exit(0) to be fully re-initialized afterward.
+ *
+ * The variable can be set to the following values:
+ * "0" - Not allowed. (default)
+ * "1" - Allowed.
+ *
+ * The value of this hint is used at runtime, so it can be changed at any time.
+ */
+#define SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY "SDL_ANDROID_ALLOW_RECREATE_ACTIVITY"
+
/**
* \brief Specify an application name.
*
diff --git a/src/core/android/SDL_android.c b/src/core/android/SDL_android.c
index 028bd1fba2ff..6a6cc83058fe 100644
--- a/src/core/android/SDL_android.c
+++ b/src/core/android/SDL_android.c
@@ -33,6 +33,7 @@
#include "../../joystick/android/SDL_sysjoystick_c.h"
#include "../../haptic/android/SDL_syshaptic_c.h"
#include "../../hidapi/android/hid.h"
+#include "../../SDL_hints_c.h"
#include <android/log.h>
#include <android/configuration.h>
@@ -166,6 +167,9 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativePermissionResult)(
JNIEnv *env, jclass cls,
jint requestCode, jboolean result);
+JNIEXPORT jboolean JNICALL SDL_JAVA_INTERFACE(nativeAllowRecreateActivity)(
+ JNIEnv *env, jclass jcls);
+
static JNINativeMethod SDLActivity_tab[] = {
{ "nativeGetVersion", "()Ljava/lang/String;", SDL_JAVA_INTERFACE(nativeGetVersion) },
{ "nativeSetupJNI", "()I", SDL_JAVA_INTERFACE(nativeSetupJNI) },
@@ -197,7 +201,8 @@ static JNINativeMethod SDLActivity_tab[] = {
{ "nativeSetenv", "(Ljava/lang/String;Ljava/lang/String;)V", SDL_JAVA_INTERFACE(nativeSetenv) },
{ "onNativeOrientationChanged", "(I)V", SDL_JAVA_INTERFACE(onNativeOrientationChanged) },
{ "nativeAddTouch", "(ILjava/lang/String;)V", SDL_JAVA_INTERFACE(nativeAddTouch) },
- { "nativePermissionResult", "(IZ)V", SDL_JAVA_INTERFACE(nativePermissionResult) }
+ { "nativePermissionResult", "(IZ)V", SDL_JAVA_INTERFACE(nativePermissionResult) },
+ { "nativeAllowRecreateActivity", "()Z", SDL_JAVA_INTERFACE(nativeAllowRecreateActivity) },
};
/* Java class SDLInputConnection */
@@ -375,6 +380,9 @@ static void Internal_Android_Destroy_AssetManager(void);
static AAssetManager *asset_manager = NULL;
static jobject javaAssetManagerRef = 0;
+/* Re-create activity hint */
+static SDL_AtomicInt bAllowRecreateActivity;
+
/*******************************************************************************
Functions called by JNI
*******************************************************************************/
@@ -525,6 +533,7 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
register_methods(env, "org/libsdl/app/SDLAudioManager", SDLAudioManager_tab, SDL_arraysize(SDLAudioManager_tab));
register_methods(env, "org/libsdl/app/SDLControllerManager", SDLControllerManager_tab, SDL_arraysize(SDLControllerManager_tab));
register_methods(env, "org/libsdl/app/HIDDeviceManager", HIDDeviceManager_tab, SDL_arraysize(HIDDeviceManager_tab));
+ SDL_AtomicSet(&bAllowRecreateActivity, SDL_FALSE);
return JNI_VERSION_1_4;
}
@@ -731,6 +740,21 @@ typedef int (*SDL_main_func)(int argc, char *argv[]);
static int run_count = 1;
+static void SDLCALL SDL_AllowRecreateActivityChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
+{
+ if (SDL_GetStringBoolean(hint, SDL_FALSE)) {
+ SDL_AtomicSet(&bAllowRecreateActivity, SDL_TRUE);
+ } else {
+ SDL_AtomicSet(&bAllowRecreateActivity, SDL_FALSE);
+ }
+}
+
+JNIEXPORT jboolean JNICALL SDL_JAVA_INTERFACE(nativeAllowRecreateActivity)(
+ JNIEnv *env, jclass jcls)
+{
+ return SDL_AtomicGet(&bAllowRecreateActivity);
+}
+
/* Start up the SDL app */
JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeRunMain)(JNIEnv *env, jclass cls, jstring library, jstring function, jobject array)
{
@@ -739,6 +763,9 @@ JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeRunMain)(JNIEnv *env, jclass cls,
void *library_handle;
__android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeRunMain() %d time", run_count);
+ if (run_count == 1) {
+ SDL_AddHintCallback(SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY, SDL_AllowRecreateActivityChanged, NULL);
+ }
run_count += 1;
/* Save JNIEnv of SDLThread */