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
This commit is contained in:
Holden Ramsey 2026-03-12 11:22:14 -04:00 committed by Sam Lantinga
parent 8554d1c23c
commit 7a6eed4ec8
7 changed files with 223 additions and 72 deletions

View file

@ -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()

View file

@ -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;
}

View file

@ -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"]))

View file

@ -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

View file

@ -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 midGetContext;
static jmethodID midGetDeviceFormFactor;
static jmethodID midGetManifestEnvironmentVariables;
static jmethodID midIsAndroidTV;
static jmethodID midIsChromebook;
static jmethodID midIsDeXMode;
static jmethodID midIsTablet;
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 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 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));
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");
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");
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");
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");
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
{

View file

@ -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);

View file

@ -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