Просмотр исходного кода

Android: decouple video/audio subsystems from JNI initialization

Allow Android embedders to use SDL without the full video/audio Java
layer by gating subsystem-specific code behind SDL_VIDEO_DISABLED and
SDL_AUDIO_DISABLED preprocessor flags.

This enables applications that only need joystick/gamepad support
(e.g. Qt-based apps like QGroundControl) to build SDL without shipping
stub Java classes for unused subsystems.

Changes:
- Split SDLActivity JNI method table into core (lifecycle, hints,
  permissions) and video (surface, input, clipboard, orientation)
- Gate SDLAudioManager and SDLInputConnection JNI registration
- Make checkJNIReady() subsystem-aware: no longer requires
  mAudioManagerClass when SDL_AUDIO_DISABLED
- Group method ID resolution by subsystem in nativeSetupJNI()
- Guard all video/audio function implementations and declarations
- Keep display orientation accessors always available (needed by camera)
- Add subsystem-selective SDL.setupJNI(int)/initialize(int) to SDL.java
  with backwards-compatible zero-arg overloads
- Guard SDL_VIDEO_DRIVER_ANDROID and related defines in
  SDL_build_config_android.h
Holden Ramsey 3 месяцев назад
Родитель
Сommit
7a6eed4ec8

+ 6 - 4
CMakeLists.txt

@@ -1499,6 +1499,9 @@ if(ANDROID)
     "${SDL3_SOURCE_DIR}/src/core/android/*.h"
   )
 
+  # Core Android code (asset I/O, logging, sensors) needs these regardless of the video subsystem.
+  sdl_link_dependency(android_core LIBS dl log android)
+
   sdl_glob_sources(
     "${SDL3_SOURCE_DIR}/src/misc/android/*.c"
     "${SDL3_SOURCE_DIR}/src/misc/android/*.h"
@@ -1629,10 +1632,6 @@ if(ANDROID)
     )
     set(HAVE_SDL_VIDEO TRUE)
 
-    # Core stuff
-    # find_library(ANDROID_DL_LIBRARY dl)
-    # FIXME failing dlopen https://github.com/android-ndk/ndk/issues/929
-    sdl_link_dependency(android_video LIBS dl log android)
     sdl_compile_definitions(PRIVATE "GL_GLEXT_PROTOTYPES")
 
     #enable gles
@@ -1663,6 +1662,9 @@ if(ANDROID)
         endif()
       endif()
     endif()
+  else()
+    # SDL_events.c pumps Android events even with video disabled; this TU supplies the stub implementations.
+    sdl_sources("${SDL3_SOURCE_DIR}/src/video/android/SDL_androidevents.c")
   endif()
 
   CheckPTHREAD()

+ 38 - 6
android-project/app/src/main/java/org/libsdl/app/SDL.java

@@ -10,26 +10,58 @@ import java.lang.reflect.Method;
 */
 public class SDL {
 
-    // This function should be called first and sets up the native code
-    // so it can call into the Java classes
+    // SDL_INIT_* values, mirrored so embedders can request a subset and skip stub classes for unused subsystems.
+    public static final int SDL_INIT_AUDIO      = 0x00000010;
+    public static final int SDL_INIT_VIDEO      = 0x00000020;
+    public static final int SDL_INIT_JOYSTICK   = 0x00000200;
+    public static final int SDL_INIT_HAPTIC     = 0x00001000;
+    public static final int SDL_INIT_GAMEPAD    = 0x00002000;
+    public static final int SDL_INIT_SENSOR     = 0x00008000;
+    public static final int SDL_INIT_CAMERA     = 0x00010000;
+
+    public static final int SDL_INIT_EVERYTHING = SDL_INIT_AUDIO | SDL_INIT_VIDEO |
+        SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMEPAD | SDL_INIT_SENSOR | SDL_INIT_CAMERA;
+
+    private static int mInitializedSubsystems = SDL_INIT_EVERYTHING;
+
     static public void setupJNI() {
+        setupJNI(SDL_INIT_EVERYTHING);
+    }
+
+    // Mask must match the native build's SDL_*_DISABLED flags: dropping SDL_INIT_AUDIO when the lib was built with audio stalls checkJNIReady() and SDL_SetMainReady() never fires.
+    static public void setupJNI(int subsystems) {
+        mInitializedSubsystems = subsystems;
+
         SDLActivity.nativeSetupJNI();
-        SDLAudioManager.nativeSetupJNI();
+
+        if ((subsystems & SDL_INIT_AUDIO) != 0) {
+            SDLAudioManager.nativeSetupJNI();
+        }
+
         SDLControllerManager.nativeSetupJNI();
     }
 
-    // This function should be called each time the activity is started
     static public void initialize() {
+        initialize(mInitializedSubsystems);
+    }
+
+    static public void initialize(int subsystems) {
         setContext(null);
 
         SDLActivity.initialize();
-        SDLAudioManager.initialize();
+
+        if ((subsystems & SDL_INIT_AUDIO) != 0) {
+            SDLAudioManager.initialize();
+        }
+
         SDLControllerManager.initialize();
     }
 
     // This function stores the current activity (SDL or not)
     static public void setContext(Activity context) {
-        SDLAudioManager.setContext(context);
+        if ((mInitializedSubsystems & SDL_INIT_AUDIO) != 0) {
+            SDLAudioManager.setContext(context);
+        }
         mContext = context;
     }
 

+ 2 - 0
build-scripts/check_android_jni.py

@@ -82,6 +82,8 @@ def collect_jni_bindings_from_c() -> dict[str, set[JniMethodBinding]]:
                     if re.match(r"\};", line):
                         in_struct = False
                         break
+                    if re.match(r"\s*(#|//)", line):
+                        continue
                     n = re.match(r"""\s*\{\s*"(?P<method>[a-zA-Z0-9_]+)"\s*,\s*"(?P<spec>[()A-Za-z0-9_/;[]+)"\s*,\s*(\(void\*\))?(HID|SDL)[_A-Z]*_JAVA_[_A-Z]*INTERFACE[_A-Z]*\((?P=method)\)\s*\},?""", line)
                     assert n, f"'{line}' does not match regex"
                     methods.add(JniMethodBinding(name=n["method"], spec=n["spec"]))

+ 2 - 0
include/build_config/SDL_build_config_android.h

@@ -178,6 +178,7 @@
 #define SDL_TIMER_UNIX 1
 
 /* Enable various video drivers */
+#ifndef SDL_VIDEO_DISABLED
 #define SDL_VIDEO_DRIVER_ANDROID 1
 
 /* Enable OpenGL ES */
@@ -196,6 +197,7 @@
 #define HAVE_GPU_OPENXR 1
 #define SDL_VIDEO_RENDER_GPU 1
 #endif
+#endif /* SDL_VIDEO_DISABLED */
 
 /* Enable system power support */
 #define SDL_POWER_ANDROID 1

+ 132 - 58
src/core/android/SDL_android.c

@@ -25,12 +25,14 @@
 #include "SDL_android.h"
 
 #include "../../events/SDL_events_c.h"
+#ifndef SDL_VIDEO_DISABLED
 #include "../../video/android/SDL_androidkeyboard.h"
 #include "../../video/android/SDL_androidmouse.h"
 #include "../../video/android/SDL_androidtouch.h"
 #include "../../video/android/SDL_androidpen.h"
 #include "../../video/android/SDL_androidvideo.h"
 #include "../../video/android/SDL_androidwindow.h"
+#endif
 #include "../../joystick/android/SDL_sysjoystick_c.h"
 #include "../../haptic/android/SDL_syshaptic_c.h"
 #include "../../hidapi/android/hid.h"
@@ -76,6 +78,7 @@ JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeRunMain)(
     JNIEnv *env, jclass cls,
     jstring library, jstring function, jobject array);
 
+#ifndef SDL_VIDEO_DISABLED
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDropFile)(
     JNIEnv *env, jclass jcls,
     jstring filename);
@@ -144,6 +147,7 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePen)(
 
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeClipboardChanged)(
     JNIEnv *env, jclass jcls);
+#endif // !SDL_VIDEO_DISABLED
 
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeLowMemory)(
     JNIEnv *env, jclass cls);
@@ -181,6 +185,7 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetenv)(
     JNIEnv *env, jclass cls,
     jstring name, jstring value);
 
+#ifndef SDL_VIDEO_DISABLED
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetNaturalOrientation)(
     JNIEnv *env, jclass cls,
     jint orientation);
@@ -196,6 +201,7 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeInsetsChanged)(
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeAddTouch)(
     JNIEnv *env, jclass cls,
     jint touchId, jstring name);
+#endif // !SDL_VIDEO_DISABLED
 
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativePermissionResult)(
     JNIEnv *env, jclass cls,
@@ -217,6 +223,23 @@ static JNINativeMethod SDLActivity_tab[] = {
     { "nativeInitMainThread", "()V", SDL_JAVA_INTERFACE(nativeInitMainThread) },
     { "nativeCleanupMainThread", "()V", SDL_JAVA_INTERFACE(nativeCleanupMainThread) },
     { "nativeRunMain", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)I", SDL_JAVA_INTERFACE(nativeRunMain) },
+    { "nativeLowMemory", "()V", SDL_JAVA_INTERFACE(nativeLowMemory) },
+    { "onNativeLocaleChanged", "()V", SDL_JAVA_INTERFACE(onNativeLocaleChanged) },
+    { "onNativeDarkModeChanged", "(Z)V", SDL_JAVA_INTERFACE(onNativeDarkModeChanged) },
+    { "nativeSendQuit", "()V", SDL_JAVA_INTERFACE(nativeSendQuit) },
+    { "nativeQuit", "()V", SDL_JAVA_INTERFACE(nativeQuit) },
+    { "nativePause", "()V", SDL_JAVA_INTERFACE(nativePause) },
+    { "nativeResume", "()V", SDL_JAVA_INTERFACE(nativeResume) },
+    { "nativeFocusChanged", "(Z)V", SDL_JAVA_INTERFACE(nativeFocusChanged) },
+    { "nativeGetHint", "(Ljava/lang/String;)Ljava/lang/String;", SDL_JAVA_INTERFACE(nativeGetHint) },
+    { "nativeGetHintBoolean", "(Ljava/lang/String;Z)Z", SDL_JAVA_INTERFACE(nativeGetHintBoolean) },
+    { "nativeSetenv", "(Ljava/lang/String;Ljava/lang/String;)V", SDL_JAVA_INTERFACE(nativeSetenv) },
+    { "nativePermissionResult", "(IZ)V", SDL_JAVA_INTERFACE(nativePermissionResult) },
+    { "nativeAllowRecreateActivity", "()Z", SDL_JAVA_INTERFACE(nativeAllowRecreateActivity) },
+    { "nativeCheckSDLThreadCounter", "()I", SDL_JAVA_INTERFACE(nativeCheckSDLThreadCounter) },
+    { "onNativeFileDialog", "(I[Ljava/lang/String;I)V", SDL_JAVA_INTERFACE(onNativeFileDialog) },
+#ifndef SDL_VIDEO_DISABLED
+    // Video/input methods, registered only when the video subsystem is enabled
     { "onNativeDropFile", "(Ljava/lang/String;)V", SDL_JAVA_INTERFACE(onNativeDropFile) },
     { "nativeSetScreenResolution", "(IIIIFF)V", SDL_JAVA_INTERFACE(nativeSetScreenResolution) },
     { "onNativeResize", "()V", SDL_JAVA_INTERFACE(onNativeResize) },
@@ -236,27 +259,14 @@ static JNINativeMethod SDLActivity_tab[] = {
     { "onNativeMouse", "(IIFFZ)V", SDL_JAVA_INTERFACE(onNativeMouse) },
     { "onNativePen", "(IIIIFFF)V", SDL_JAVA_INTERFACE(onNativePen) },
     { "onNativeClipboardChanged", "()V", SDL_JAVA_INTERFACE(onNativeClipboardChanged) },
-    { "nativeLowMemory", "()V", SDL_JAVA_INTERFACE(nativeLowMemory) },
-    { "onNativeLocaleChanged", "()V", SDL_JAVA_INTERFACE(onNativeLocaleChanged) },
-    { "onNativeDarkModeChanged", "(Z)V", SDL_JAVA_INTERFACE(onNativeDarkModeChanged) },
-    { "nativeSendQuit", "()V", SDL_JAVA_INTERFACE(nativeSendQuit) },
-    { "nativeQuit", "()V", SDL_JAVA_INTERFACE(nativeQuit) },
-    { "nativePause", "()V", SDL_JAVA_INTERFACE(nativePause) },
-    { "nativeResume", "()V", SDL_JAVA_INTERFACE(nativeResume) },
-    { "nativeFocusChanged", "(Z)V", SDL_JAVA_INTERFACE(nativeFocusChanged) },
-    { "nativeGetHint", "(Ljava/lang/String;)Ljava/lang/String;", SDL_JAVA_INTERFACE(nativeGetHint) },
-    { "nativeGetHintBoolean", "(Ljava/lang/String;Z)Z", SDL_JAVA_INTERFACE(nativeGetHintBoolean) },
-    { "nativeSetenv", "(Ljava/lang/String;Ljava/lang/String;)V", SDL_JAVA_INTERFACE(nativeSetenv) },
     { "nativeSetNaturalOrientation", "(I)V", SDL_JAVA_INTERFACE(nativeSetNaturalOrientation) },
     { "onNativeRotationChanged", "(I)V", SDL_JAVA_INTERFACE(onNativeRotationChanged) },
     { "onNativeInsetsChanged", "(IIII)V", SDL_JAVA_INTERFACE(onNativeInsetsChanged) },
-    { "nativeAddTouch", "(ILjava/lang/String;)V", SDL_JAVA_INTERFACE(nativeAddTouch) },
-    { "nativePermissionResult", "(IZ)V", SDL_JAVA_INTERFACE(nativePermissionResult) },
-    { "nativeAllowRecreateActivity", "()Z", SDL_JAVA_INTERFACE(nativeAllowRecreateActivity) },
-    { "nativeCheckSDLThreadCounter", "()I", SDL_JAVA_INTERFACE(nativeCheckSDLThreadCounter) },
-    { "onNativeFileDialog", "(I[Ljava/lang/String;I)V", SDL_JAVA_INTERFACE(onNativeFileDialog) }
+    { "nativeAddTouch", "(ILjava/lang/String;)V", SDL_JAVA_INTERFACE(nativeAddTouch) }
+#endif // !SDL_VIDEO_DISABLED
 };
 
+#ifndef SDL_VIDEO_DISABLED
 // Java class SDLInputConnection
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeCommitText)(
     JNIEnv *env, jclass cls,
@@ -270,7 +280,9 @@ static JNINativeMethod SDLInputConnection_tab[] = {
     { "nativeCommitText", "(Ljava/lang/String;I)V", SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeCommitText) },
     { "nativeGenerateScancodeForUnichar", "(C)V", SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeGenerateScancodeForUnichar) }
 };
+#endif // !SDL_VIDEO_DISABLED
 
+#ifndef SDL_AUDIO_DISABLED
 // Java class SDLAudioManager
 JNIEXPORT void JNICALL SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI)(
     JNIEnv *env, jclass jcls);
@@ -288,6 +300,7 @@ static JNINativeMethod SDLAudioManager_tab[] = {
     { "nativeAddAudioDevice", "(ZLjava/lang/String;I)V", SDL_JAVA_AUDIO_INTERFACE(nativeAddAudioDevice) },
     { "nativeRemoveAudioDevice", "(ZI)V", SDL_JAVA_AUDIO_INTERFACE(nativeRemoveAudioDevice) }
 };
+#endif // !SDL_AUDIO_DISABLED
 
 // Java class SDLControllerManager
 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI)(
@@ -364,27 +377,32 @@ static JavaVM *mJavaVM = NULL;
 // Main activity
 static jclass mActivityClass;
 
-// method signatures
-static jmethodID midClipboardGetText;
-static jmethodID midClipboardHasText;
-static jmethodID midClipboardSetText;
-static jmethodID midCreateCustomCursor;
-static jmethodID midDestroyCustomCursor;
 static jmethodID midGetContext;
 static jmethodID midGetDeviceFormFactor;
 static jmethodID midGetManifestEnvironmentVariables;
-static jmethodID midGetNativeSurface;
-static jmethodID midInitTouch;
 static jmethodID midIsAndroidTV;
 static jmethodID midIsChromebook;
 static jmethodID midIsDeXMode;
 static jmethodID midIsTablet;
-static jmethodID midManualBackButton;
-static jmethodID midMinimizeWindow;
 static jmethodID midOpenURL;
 static jmethodID midRequestPermission;
 static jmethodID midShowToast;
 static jmethodID midSendMessage;
+static jmethodID midOpenFileDescriptor;
+static jmethodID midShowFileDialog;
+static jmethodID midGetPreferredLocales;
+
+#ifndef SDL_VIDEO_DISABLED
+// Video/surface method signatures
+static jmethodID midClipboardGetText;
+static jmethodID midClipboardHasText;
+static jmethodID midClipboardSetText;
+static jmethodID midCreateCustomCursor;
+static jmethodID midDestroyCustomCursor;
+static jmethodID midGetNativeSurface;
+static jmethodID midInitTouch;
+static jmethodID midManualBackButton;
+static jmethodID midMinimizeWindow;
 static jmethodID midSetActivityTitle;
 static jmethodID midSetCustomCursor;
 static jmethodID midSetOrientation;
@@ -394,10 +412,9 @@ static jmethodID midSetWindowStyle;
 static jmethodID midShouldMinimizeOnFocusLoss;
 static jmethodID midShowTextInput;
 static jmethodID midSupportsRelativeMouse;
-static jmethodID midOpenFileDescriptor;
-static jmethodID midShowFileDialog;
-static jmethodID midGetPreferredLocales;
+#endif // !SDL_VIDEO_DISABLED
 
+#ifndef SDL_AUDIO_DISABLED
 // audio manager
 static jclass mAudioManagerClass;
 
@@ -405,6 +422,7 @@ static jclass mAudioManagerClass;
 static jmethodID midRegisterAudioDeviceCallback;
 static jmethodID midUnregisterAudioDeviceCallback;
 static jmethodID midAudioSetThreadPriority;
+#endif // !SDL_AUDIO_DISABLED
 
 // controller manager
 static jclass mControllerManagerClass;
@@ -584,8 +602,12 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
     }
 
     register_methods(env, "org/libsdl/app/SDLActivity", SDLActivity_tab, SDL_arraysize(SDLActivity_tab));
+#ifndef SDL_VIDEO_DISABLED
     register_methods(env, "org/libsdl/app/SDLInputConnection", SDLInputConnection_tab, SDL_arraysize(SDLInputConnection_tab));
+#endif
+#ifndef SDL_AUDIO_DISABLED
     register_methods(env, "org/libsdl/app/SDLAudioManager", SDLAudioManager_tab, SDL_arraysize(SDLAudioManager_tab));
+#endif
     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));
 
@@ -594,11 +616,17 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
 
 void checkJNIReady(void)
 {
-    if (!mActivityClass || !mAudioManagerClass || !mControllerManagerClass) {
+    if (!mActivityClass || !mControllerManagerClass) {
         // We aren't fully initialized, let's just return.
         return;
     }
 
+#ifndef SDL_AUDIO_DISABLED
+    if (!mAudioManagerClass) {
+        return;
+    }
+#endif
+
     SDL_SetMainReady();
 }
 
@@ -656,26 +684,48 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cl
 
     mActivityClass = (jclass)((*env)->NewGlobalRef(env, cls));
 
-    midClipboardGetText = (*env)->GetStaticMethodID(env, mActivityClass, "clipboardGetText", "()Ljava/lang/String;");
-    midClipboardHasText = (*env)->GetStaticMethodID(env, mActivityClass, "clipboardHasText", "()Z");
-    midClipboardSetText = (*env)->GetStaticMethodID(env, mActivityClass, "clipboardSetText", "(Ljava/lang/String;)V");
-    midCreateCustomCursor = (*env)->GetStaticMethodID(env, mActivityClass, "createCustomCursor", "([IIIII)I");
-    midDestroyCustomCursor = (*env)->GetStaticMethodID(env, mActivityClass, "destroyCustomCursor", "(I)V");
     midGetContext = (*env)->GetStaticMethodID(env, mActivityClass, "getContext", "()Landroid/app/Activity;");
     midGetDeviceFormFactor = (*env)->GetStaticMethodID(env, mActivityClass, "getDeviceFormFactor", "()Ljava/lang/String;");
     midGetManifestEnvironmentVariables = (*env)->GetStaticMethodID(env, mActivityClass, "getManifestEnvironmentVariables", "()Z");
-    midGetNativeSurface = (*env)->GetStaticMethodID(env, mActivityClass, "getNativeSurface", "()Landroid/view/Surface;");
-    midInitTouch = (*env)->GetStaticMethodID(env, mActivityClass, "initTouch", "()V");
     midIsAndroidTV = (*env)->GetStaticMethodID(env, mActivityClass, "isAndroidTV", "()Z");
     midIsChromebook = (*env)->GetStaticMethodID(env, mActivityClass, "isChromebook", "()Z");
     midIsDeXMode = (*env)->GetStaticMethodID(env, mActivityClass, "isDeXMode", "()Z");
     midIsTablet = (*env)->GetStaticMethodID(env, mActivityClass, "isTablet", "()Z");
-    midManualBackButton = (*env)->GetStaticMethodID(env, mActivityClass, "manualBackButton", "()V");
-    midMinimizeWindow = (*env)->GetStaticMethodID(env, mActivityClass, "minimizeWindow", "()V");
     midOpenURL = (*env)->GetStaticMethodID(env, mActivityClass, "openURL", "(Ljava/lang/String;)Z");
     midRequestPermission = (*env)->GetStaticMethodID(env, mActivityClass, "requestPermission", "(Ljava/lang/String;I)V");
     midShowToast = (*env)->GetStaticMethodID(env, mActivityClass, "showToast", "(Ljava/lang/String;IIII)Z");
     midSendMessage = (*env)->GetStaticMethodID(env, mActivityClass, "sendMessage", "(II)Z");
+    midOpenFileDescriptor = (*env)->GetStaticMethodID(env, mActivityClass, "openFileDescriptor", "(Ljava/lang/String;Ljava/lang/String;)I");
+    midShowFileDialog = (*env)->GetStaticMethodID(env, mActivityClass, "showFileDialog", "([Ljava/lang/String;ZILjava/lang/String;I)Z");
+    midGetPreferredLocales = (*env)->GetStaticMethodID(env, mActivityClass, "getPreferredLocales", "()Ljava/lang/String;");
+
+    if (!midGetContext ||
+        !midGetDeviceFormFactor ||
+        !midGetManifestEnvironmentVariables ||
+        !midIsAndroidTV ||
+        !midIsChromebook ||
+        !midIsDeXMode ||
+        !midIsTablet ||
+        !midOpenURL ||
+        !midRequestPermission ||
+        !midShowToast ||
+        !midSendMessage ||
+        !midOpenFileDescriptor ||
+        !midShowFileDialog ||
+        !midGetPreferredLocales) {
+        __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some core Java callbacks, do you have the latest version of SDLActivity.java?");
+    }
+
+#ifndef SDL_VIDEO_DISABLED
+    midClipboardGetText = (*env)->GetStaticMethodID(env, mActivityClass, "clipboardGetText", "()Ljava/lang/String;");
+    midClipboardHasText = (*env)->GetStaticMethodID(env, mActivityClass, "clipboardHasText", "()Z");
+    midClipboardSetText = (*env)->GetStaticMethodID(env, mActivityClass, "clipboardSetText", "(Ljava/lang/String;)V");
+    midCreateCustomCursor = (*env)->GetStaticMethodID(env, mActivityClass, "createCustomCursor", "([IIIII)I");
+    midDestroyCustomCursor = (*env)->GetStaticMethodID(env, mActivityClass, "destroyCustomCursor", "(I)V");
+    midGetNativeSurface = (*env)->GetStaticMethodID(env, mActivityClass, "getNativeSurface", "()Landroid/view/Surface;");
+    midInitTouch = (*env)->GetStaticMethodID(env, mActivityClass, "initTouch", "()V");
+    midManualBackButton = (*env)->GetStaticMethodID(env, mActivityClass, "manualBackButton", "()V");
+    midMinimizeWindow = (*env)->GetStaticMethodID(env, mActivityClass, "minimizeWindow", "()V");
     midSetActivityTitle = (*env)->GetStaticMethodID(env, mActivityClass, "setActivityTitle", "(Ljava/lang/String;)Z");
     midSetCustomCursor = (*env)->GetStaticMethodID(env, mActivityClass, "setCustomCursor", "(I)Z");
     midSetOrientation = (*env)->GetStaticMethodID(env, mActivityClass, "setOrientation", "(IIZLjava/lang/String;)V");
@@ -685,30 +735,16 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cl
     midShouldMinimizeOnFocusLoss = (*env)->GetStaticMethodID(env, mActivityClass, "shouldMinimizeOnFocusLoss", "()Z");
     midShowTextInput = (*env)->GetStaticMethodID(env, mActivityClass, "showTextInput", "(IIIII)Z");
     midSupportsRelativeMouse = (*env)->GetStaticMethodID(env, mActivityClass, "supportsRelativeMouse", "()Z");
-    midOpenFileDescriptor = (*env)->GetStaticMethodID(env, mActivityClass, "openFileDescriptor", "(Ljava/lang/String;Ljava/lang/String;)I");
-    midShowFileDialog = (*env)->GetStaticMethodID(env, mActivityClass, "showFileDialog", "([Ljava/lang/String;ZILjava/lang/String;I)Z");
-    midGetPreferredLocales = (*env)->GetStaticMethodID(env, mActivityClass, "getPreferredLocales", "()Ljava/lang/String;");
 
     if (!midClipboardGetText ||
         !midClipboardHasText ||
         !midClipboardSetText ||
         !midCreateCustomCursor ||
         !midDestroyCustomCursor ||
-        !midGetContext ||
-        !midGetDeviceFormFactor ||
-        !midGetManifestEnvironmentVariables ||
         !midGetNativeSurface ||
         !midInitTouch ||
-        !midIsAndroidTV ||
-        !midIsChromebook ||
-        !midIsDeXMode ||
-        !midIsTablet ||
         !midManualBackButton ||
         !midMinimizeWindow ||
-        !midOpenURL ||
-        !midRequestPermission ||
-        !midShowToast ||
-        !midSendMessage ||
         !midSetActivityTitle ||
         !midSetCustomCursor ||
         !midSetOrientation ||
@@ -717,16 +753,15 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cl
         !midSetWindowStyle ||
         !midShouldMinimizeOnFocusLoss ||
         !midShowTextInput ||
-        !midSupportsRelativeMouse ||
-        !midOpenFileDescriptor ||
-        !midShowFileDialog ||
-        !midGetPreferredLocales) {
-        __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLActivity.java?");
+        !midSupportsRelativeMouse) {
+        __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some video Java callbacks, do you have the latest version of SDLActivity.java?");
     }
+#endif // !SDL_VIDEO_DISABLED
 
     checkJNIReady();
 }
 
+#ifndef SDL_AUDIO_DISABLED
 // Audio initialization -- called before SDL_main() to initialize JNI bindings
 JNIEXPORT void JNICALL SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cls)
 {
@@ -750,6 +785,7 @@ JNIEXPORT void JNICALL SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI)(JNIEnv *env, jcl
 
     checkJNIReady();
 }
+#endif // !SDL_AUDIO_DISABLED
 
 // Controller initialization -- called before SDL_main() to initialize JNI bindings
 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cls)
@@ -1043,6 +1079,7 @@ void Android_UnlockActivityMutex(void)
     SDL_UnlockMutex(Android_ActivityMutex);
 }
 
+#ifndef SDL_VIDEO_DISABLED
 // Drop file
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDropFile)(
     JNIEnv *env, jclass jcls,
@@ -1142,7 +1179,9 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeAddTouch)(
 
     (*env)->ReleaseStringUTFChars(env, name, utfname);
 }
+#endif // !SDL_VIDEO_DISABLED
 
+#ifndef SDL_AUDIO_DISABLED
 JNIEXPORT void JNICALL
 SDL_JAVA_AUDIO_INTERFACE(nativeAddAudioDevice)(JNIEnv *env, jclass jcls, jboolean recording,
                                          jstring name, jint device_id)
@@ -1170,6 +1209,7 @@ SDL_JAVA_AUDIO_INTERFACE(nativeRemoveAudioDevice)(JNIEnv *env, jclass jcls, jboo
     }
 #endif
 }
+#endif // !SDL_AUDIO_DISABLED
 
 // Paddown
 JNIEXPORT jboolean JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadDown)(
@@ -1273,6 +1313,7 @@ JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveHaptic)(
 #endif
 }
 
+#ifndef SDL_VIDEO_DISABLED
 // Called from surfaceCreated()
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceCreated)(JNIEnv *env, jclass jcls)
 {
@@ -1498,6 +1539,7 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeClipboardChanged)(
     // TODO: compute new mime types
     SDL_SendClipboardUpdate(false, NULL, 0);
 }
+#endif // !SDL_VIDEO_DISABLED
 
 // Low memory
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeLowMemory)(
@@ -1518,7 +1560,11 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeLocaleChanged)(
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDarkModeChanged)(
     JNIEnv *env, jclass cls, jboolean enabled)
 {
+#ifndef SDL_VIDEO_DISABLED
     Android_SetDarkMode(enabled);
+#else
+    (void)enabled;
+#endif
 }
 
 // Send Quit event to "SDLThread" thread
@@ -1584,10 +1630,14 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeFocusChanged)(
 {
     SDL_LockMutex(Android_ActivityMutex);
 
+#ifndef SDL_VIDEO_DISABLED
     if (Android_Window) {
         __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeFocusChanged()");
         SDL_SendWindowEvent(Android_Window, (hasFocus ? SDL_EVENT_WINDOW_FOCUS_GAINED : SDL_EVENT_WINDOW_FOCUS_LOST), 0, 0);
     }
+#else
+    (void)hasFocus;
+#endif
 
     SDL_UnlockMutex(Android_ActivityMutex);
 }
@@ -1705,6 +1755,7 @@ static void LocalReferenceHolder_Cleanup(struct LocalReferenceHolder *refholder)
     }
 }
 
+#ifndef SDL_VIDEO_DISABLED
 ANativeWindow *Android_JNI_GetNativeWindow(void)
 {
     ANativeWindow *anw = NULL;
@@ -1744,6 +1795,9 @@ void Android_JNI_SetOrientation(int w, int h, int resizable, const char *hint)
     (*env)->DeleteLocalRef(env, jhint);
 }
 
+#endif // !SDL_VIDEO_DISABLED
+
+// Outside the video guard: the camera driver reads these cached values (only written by the video Java layer).
 SDL_DisplayOrientation Android_JNI_GetDisplayNaturalOrientation(void)
 {
     return displayNaturalOrientation;
@@ -1754,6 +1808,7 @@ SDL_DisplayOrientation Android_JNI_GetDisplayCurrentOrientation(void)
     return displayCurrentOrientation;
 }
 
+#ifndef SDL_VIDEO_DISABLED
 void Android_JNI_MinimizeWindow(void)
 {
     JNIEnv *env = Android_JNI_GetEnv();
@@ -1766,6 +1821,14 @@ bool Android_JNI_ShouldMinimizeOnFocusLoss(void)
     return (*env)->CallStaticBooleanMethod(env, mActivityClass, midShouldMinimizeOnFocusLoss);
 }
 
+#else
+bool Android_JNI_ShouldMinimizeOnFocusLoss(void)
+{
+    return false;
+}
+#endif // !SDL_VIDEO_DISABLED
+
+#ifndef SDL_AUDIO_DISABLED
 /*
  * Audio support
  */
@@ -1793,6 +1856,7 @@ void Android_AudioThreadInit(SDL_AudioDevice *device)
 {
     Android_JNI_AudioSetThreadPriority((int) device->recording, (int)device->instance_id);
 }
+#endif // !SDL_AUDIO_DISABLED
 
 // Test for an exception and call SDL_SetError with its detail if one occurs
 // If the parameter silent is truthy then SDL_SetError() will not be called.
@@ -2500,6 +2564,7 @@ bool Android_JNI_GetAssetPathInfo(const char *path, SDL_PathInfo *info)
     return true;
 }
 
+#ifndef SDL_VIDEO_DISABLED
 bool Android_JNI_SetClipboardText(const char *text)
 {
     JNIEnv *env = Android_JNI_GetEnv();
@@ -2533,6 +2598,7 @@ bool Android_JNI_HasClipboardText(void)
     JNIEnv *env = Android_JNI_GetEnv();
     return (*env)->CallStaticBooleanMethod(env, mActivityClass, midClipboardHasText);
 }
+#endif // !SDL_VIDEO_DISABLED
 
 /* returns 0 on success or -1 on error (others undefined then)
  * returns truthy or falsy value in plugged, charged and battery
@@ -2654,12 +2720,14 @@ int Android_JNI_GetPowerInfo(int *plugged, int *charged, int *battery, int *seco
     return 0;
 }
 
+#ifndef SDL_VIDEO_DISABLED
 // Add all touch devices
 void Android_JNI_InitTouch(void)
 {
     JNIEnv *env = Android_JNI_GetEnv();
     (*env)->CallStaticVoidMethod(env, mActivityClass, midInitTouch);
 }
+#endif // !SDL_VIDEO_DISABLED
 
 void Android_JNI_DetectDevices(void)
 {
@@ -2726,6 +2794,7 @@ bool Android_JNI_SuspendScreenSaver(bool suspend)
     return Android_JNI_SendMessage(COMMAND_SET_KEEP_SCREEN_ON, (suspend == false) ? 0 : 1);
 }
 
+#ifndef SDL_VIDEO_DISABLED
 void Android_JNI_ShowScreenKeyboard(int input_type, SDL_Rect *inputRect)
 {
     JNIEnv *env = Android_JNI_GetEnv();
@@ -2743,6 +2812,7 @@ void Android_JNI_HideScreenKeyboard(void)
     const int COMMAND_TEXTEDIT_HIDE = 3;
     Android_JNI_SendMessage(COMMAND_TEXTEDIT_HIDE, 0);
 }
+#endif // !SDL_VIDEO_DISABLED
 
 bool Android_JNI_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID)
 {
@@ -2937,8 +3007,10 @@ bool SDL_IsDeXMode(void)
 
 void SDL_SendAndroidBackButton(void)
 {
+#ifndef SDL_VIDEO_DISABLED
     JNIEnv *env = Android_JNI_GetEnv();
     (*env)->CallStaticVoidMethod(env, mActivityClass, midManualBackButton);
+#endif
 }
 
 const char *SDL_GetAndroidInternalStoragePath(void)
@@ -3147,6 +3219,7 @@ void Android_JNI_GetManifestEnvironmentVariables(void)
     }
 }
 
+#ifndef SDL_VIDEO_DISABLED
 int Android_JNI_CreateCustomCursor(SDL_Surface *surface, int hot_x, int hot_y)
 {
     JNIEnv *env = Android_JNI_GetEnv();
@@ -3192,6 +3265,7 @@ bool Android_JNI_SetRelativeMouseEnabled(bool enabled)
     JNIEnv *env = Android_JNI_GetEnv();
     return (*env)->CallStaticBooleanMethod(env, mActivityClass, midSetRelativeMouseEnabled, (enabled == 1));
 }
+#endif // !SDL_VIDEO_DISABLED
 
 typedef struct NativePermissionRequestInfo
 {

+ 16 - 0
src/core/android/SDL_android.h

@@ -30,13 +30,17 @@ extern "C" {
 /* *INDENT-ON* */
 #endif
 
+#ifndef SDL_VIDEO_DISABLED
 #include <EGL/eglplatform.h>
 #include <android/native_window_jni.h>
+#endif
 
+#ifndef SDL_AUDIO_DISABLED
 #include "../../audio/SDL_sysaudio.h"
 
 // this appears to be broken right now (on Android, not SDL, I think...?).
 #define ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES 0
+#endif
 
 // Life cycle
 typedef enum
@@ -57,6 +61,7 @@ void Android_UnlockActivityMutex(void);
 
 void Android_SetAllowRecreateActivity(bool enabled);
 
+#ifndef SDL_VIDEO_DISABLED
 // Interface from the SDL library into the Android Java activity
 extern void Android_JNI_SetActivityTitle(const char *title);
 extern void Android_JNI_SetWindowStyle(bool fullscreen);
@@ -68,13 +73,18 @@ extern void Android_JNI_ShowScreenKeyboard(int input_type, SDL_Rect *inputRect);
 extern void Android_JNI_HideScreenKeyboard(void);
 extern ANativeWindow *Android_JNI_GetNativeWindow(void);
 
+#endif // !SDL_VIDEO_DISABLED
+
+// Kept outside the video guard for the camera driver; stays SDL_ORIENTATION_UNKNOWN when video is disabled (only the video Java layer updates it).
 extern SDL_DisplayOrientation Android_JNI_GetDisplayNaturalOrientation(void);
 extern SDL_DisplayOrientation Android_JNI_GetDisplayCurrentOrientation(void);
 
+#ifndef SDL_AUDIO_DISABLED
 // Audio support
 void Android_StartAudioHotplug(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording);
 void Android_StopAudioHotplug(void);
 extern void Android_AudioThreadInit(SDL_AudioDevice *device);
+#endif // !SDL_AUDIO_DISABLED
 
 // Detecting device type
 extern bool Android_IsDeXMode(void);
@@ -93,10 +103,12 @@ bool Android_JNI_GetAssetPathInfo(const char *path, SDL_PathInfo *info);
 void Android_JNI_GetManifestEnvironmentVariables(void);
 int Android_JNI_OpenFileDescriptor(const char *uri, const char *mode);
 
+#ifndef SDL_VIDEO_DISABLED
 // Clipboard support
 bool Android_JNI_SetClipboardText(const char *text);
 char *Android_JNI_GetClipboardText(void);
 bool Android_JNI_HasClipboardText(void);
+#endif // !SDL_VIDEO_DISABLED
 
 // Power support
 int Android_JNI_GetPowerInfo(int *plugged, int *charged, int *battery, int *seconds, int *percent);
@@ -115,8 +127,10 @@ void Android_JNI_HapticStop(int device_id);
 // Video
 bool Android_JNI_SuspendScreenSaver(bool suspend);
 
+#ifndef SDL_VIDEO_DISABLED
 // Touch support
 void Android_JNI_InitTouch(void);
+#endif // !SDL_VIDEO_DISABLED
 
 // Threads
 #include <jni.h>
@@ -132,6 +146,7 @@ bool Android_JNI_SendMessage(int command, int param);
 // MessageBox
 bool Android_JNI_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID);
 
+#ifndef SDL_VIDEO_DISABLED
 // Cursor support
 int Android_JNI_CreateCustomCursor(SDL_Surface *surface, int hot_x, int hot_y);
 void Android_JNI_DestroyCustomCursor(int cursorID);
@@ -141,6 +156,7 @@ bool Android_JNI_SetSystemCursor(int cursorID);
 // Relative mouse support
 bool Android_JNI_SupportsRelativeMouse(void);
 bool Android_JNI_SetRelativeMouseEnabled(bool enabled);
+#endif // !SDL_VIDEO_DISABLED
 
 // Show toast notification
 bool Android_JNI_ShowToast(const char *message, int duration, int gravity, int xOffset, int yOffset);

+ 23 - 0
src/video/android/SDL_androidevents.c

@@ -264,4 +264,27 @@ void Android_QuitEvents(void)
     Android_EventsInitialized = false;
 }
 
+#else
+
+#include "../../core/android/SDL_android.h"
+
+void Android_InitEvents(void)
+{
+}
+
+void Android_PumpEvents(Sint64 timeoutNS)
+{
+    (void)timeoutNS;
+}
+
+bool Android_WaitActiveAndLockActivity(void)
+{
+    Android_LockActivityMutex();
+    return true;
+}
+
+void Android_QuitEvents(void)
+{
+}
+
 #endif // SDL_VIDEO_DRIVER_ANDROID