From 2919e1899fff80106084e8ba10264733c546ba0d Mon Sep 17 00:00:00 2001 From: Holden Ramsey Date: Sat, 27 Jun 2026 13:53:46 -0400 Subject: [PATCH] Android: decouple JNI setup from unused subsystems Query the natively compiled-in subsystems at runtime so the Java side only registers and initializes the managers that exist, fixing UnsatisfiedLinkError when SDL is built with a subsystem disabled (e.g. -DSDL_AUDIO_DISABLED). - Add SDL.setupJNI(int subsystems) plus SDL_INIT_* Java constants; SDLActivity passes an overridable getInitSubsystems() mask so custom activities can skip Java-side setup for unused subsystems. - Distinguish compiled from requested subsystems: JNI registration follows nativeGetCompiledSubsystems(), while manager initialization and surface/layout creation follow the requested-and-compiled mask. - Gate HIDDeviceManager.acquire() (USB/BLE device scanning) on a new nativeIsHIDAPIEnabled() query so HIDAPI-less builds never touch it. - Gate SDLControllerManager.initializeDeviceListener() and the joystick key-event path on the controller subsystem; gate the clipboard handler on video, mirroring the native guards. - Guard the SDLControllerManager JNI bindings with SDL_ANDROID_NEED_CONTROLLER_MANAGER (joystick or haptic enabled). - Skip layout updates in ShowTextInputTask when no layout exists (video disabled). - Reorganize SDL_android.h/.c into guarded per-subsystem groups with matching ordering. --- .../app/src/main/java/org/libsdl/app/SDL.java | 36 +- .../main/java/org/libsdl/app/SDLActivity.java | 92 +- .../org/libsdl/app/SDLControllerManager.java | 36 +- src/core/android/SDL_android.c | 2525 +++++++++-------- src/core/android/SDL_android.h | 92 +- src/joystick/android/SDL_sysjoystick.c | 4 + 6 files changed, 1476 insertions(+), 1309 deletions(-) diff --git a/android-project/app/src/main/java/org/libsdl/app/SDL.java b/android-project/app/src/main/java/org/libsdl/app/SDL.java index 097eb8cc87..a04f2c996c 100644 --- a/android-project/app/src/main/java/org/libsdl/app/SDL.java +++ b/android-project/app/src/main/java/org/libsdl/app/SDL.java @@ -22,23 +22,29 @@ public class SDL { 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; + // SDLControllerManager backs all three of these, so it is set up when any are present. + private static final int SDL_INIT_CONTROLLER = SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD | SDL_INIT_HAPTIC; + private static int mInitializedSubsystems = SDL_INIT_EVERYTHING; + private static int mCompiledSubsystems = 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(); - if ((subsystems & SDL_INIT_AUDIO) != 0) { + mCompiledSubsystems = SDLActivity.nativeGetCompiledSubsystems(); + mInitializedSubsystems = (subsystems & mCompiledSubsystems); + + if (isSubsystemCompiled(SDL_INIT_AUDIO)) { SDLAudioManager.nativeSetupJNI(); } - SDLControllerManager.nativeSetupJNI(); + if (isSubsystemCompiled(SDL_INIT_CONTROLLER)) { + SDLControllerManager.nativeSetupJNI(); + } } static public void initialize() { @@ -50,16 +56,30 @@ public class SDL { SDLActivity.initialize(); - if ((subsystems & SDL_INIT_AUDIO) != 0) { + if (isSubsystemCompiled(SDL_INIT_AUDIO)) { SDLAudioManager.initialize(); } - SDLControllerManager.initialize(); + if (isSubsystemCompiled(SDL_INIT_CONTROLLER)) { + SDLControllerManager.initialize(); + } + } + + static boolean isSubsystemInitialized(int subsystem) { + return (mInitializedSubsystems & subsystem) != 0; + } + + static boolean isSubsystemCompiled(int subsystem) { + return (mCompiledSubsystems & subsystem) != 0; + } + + static boolean isControllerManagerReady() { + return isSubsystemInitialized(SDL_INIT_CONTROLLER); } // This function stores the current activity (SDL or not) static public void setContext(Activity context) { - if ((mInitializedSubsystems & SDL_INIT_AUDIO) != 0) { + if (isSubsystemCompiled(SDL_INIT_AUDIO)) { SDLAudioManager.setContext(context); } mContext = context; 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 00df5f0864..31c3b267c2 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 @@ -337,6 +337,20 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh return new String[0]; } + /** + * This method returns the SDL_INIT_* subsystems this activity should set up on the Java side. + * It can be overridden to skip the surface/layout creation, device listener and input event + * routing for unused subsystems. Manager JNI setup always follows the subsystems compiled + * into the native library, regardless of this mask. The native app must not SDL_Init() a + * subsystem excluded here: it would initialize without Java-side events (no window surface, + * no controller hotplug or input). Called from onCreate(); overrides must not depend on + * state the subclass assigns after super.onCreate(). + * @return mask of SDL.SDL_INIT_* values. + */ + protected int getInitSubsystems() { + return SDL.SDL_INIT_EVERYTHING; + } + public static void initialize() { // The static nature of the singleton and Android quirkyness force us to initialize everything here // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values @@ -458,7 +472,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh } // Set up JNI - SDL.setupJNI(); + SDL.setupJNI(getInitSubsystems()); // Initialize state SDL.initialize(); @@ -467,22 +481,30 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh mSingleton = this; SDL.setContext(this); - SDLControllerManager.initializeDeviceListener(); + if (SDL.isControllerManagerReady()) { + SDLControllerManager.initializeDeviceListener(); + } - mClipboardHandler = new SDLClipboardHandler(); + if (SDL.isSubsystemCompiled(SDL.SDL_INIT_VIDEO)) { + mClipboardHandler = new SDLClipboardHandler(); + } - mHIDDeviceManager = HIDDeviceManager.acquire(this); + if (nativeIsHIDAPIEnabled()) { + mHIDDeviceManager = HIDDeviceManager.acquire(this); + } // Set up the surface - mSurface = createSDLSurface(this); + if (SDL.isSubsystemInitialized(SDL.SDL_INIT_VIDEO)) { + mSurface = createSDLSurface(this); - mLayout = new RelativeLayout(this); - mLayout.addView(mSurface); + mLayout = new RelativeLayout(this); + mLayout.addView(mSurface); - // Get our current screen orientation and pass it down. - SDLActivity.nativeSetNaturalOrientation(SDLActivity.getNaturalOrientation()); - mCurrentRotation = SDLActivity.getCurrentRotation(); - SDLActivity.onNativeRotationChanged(mCurrentRotation); + // Get our current screen orientation and pass it down. + SDLActivity.nativeSetNaturalOrientation(SDLActivity.getNaturalOrientation()); + mCurrentRotation = SDLActivity.getCurrentRotation(); + SDLActivity.onNativeRotationChanged(mCurrentRotation); + } try { if (Build.VERSION.SDK_INT < 24 /* Android 7.0 (N) */) { @@ -502,19 +524,22 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh break; } - setContentView(mLayout); - - setWindowStyle(false); + if (mLayout != null) { + setContentView(mLayout); + setWindowStyle(false); + } getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(this); // Get filename from "Open with" of another application - Intent intent = getIntent(); - if (intent != null && intent.getData() != null) { - String filename = intent.getData().getPath(); - if (filename != null) { - Log.v(TAG, "Got filename: " + filename); - SDLActivity.onNativeDropFile(filename); + if (SDL.isSubsystemInitialized(SDL.SDL_INIT_VIDEO)) { + Intent intent = getIntent(); + if (intent != null && intent.getData() != null) { + String filename = intent.getData().getPath(); + if (filename != null) { + Log.v(TAG, "Got filename: " + filename); + SDLActivity.onNativeDropFile(filename); + } } } } @@ -886,21 +911,27 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh // Try a transition to resumed state if (mNextNativeState == NativeState.RESUMED) { - if (mSurface.mIsSurfaceReady && (mHasFocus || mHasMultiWindow) && mIsResumedCalled) { + boolean readyToRun = (mSurface == null) ? mIsResumedCalled + : (mSurface.mIsSurfaceReady && (mHasFocus || mHasMultiWindow) && mIsResumedCalled); + if (readyToRun) { if (mSDLThread == null) { // This is the entry point to the C app. // Start up the C app thread and enable sensor input for the first time // FIXME: Why aren't we enabling sensor input at start? mSDLThread = new Thread(new SDLMain(), "SDLThread"); - mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, true); + if (mSurface != null) { + mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, true); + } mSDLThread.start(); // No nativeResume(), don't signal Android_ResumeSem } else { nativeResume(); } - mSurface.handleResume(); + if (mSurface != null) { + mSurface.handleResume(); + } mCurrentNativeState = mNextNativeState; } @@ -1058,7 +1089,8 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh DisplayMetrics realMetrics = new DisplayMetrics(); display.getRealMetrics(realMetrics); - boolean bFullscreenLayout = ((realMetrics.widthPixels == mSurface.getWidth()) && + boolean bFullscreenLayout = (mSurface != null) && + ((realMetrics.widthPixels == mSurface.getWidth()) && (realMetrics.heightPixels == mSurface.getHeight())); if ((Integer) data == 1) { @@ -1102,6 +1134,8 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh // C functions we call public static native String nativeGetVersion(); public static native void nativeSetupJNI(); + public static native int nativeGetCompiledSubsystems(); + public static native boolean nativeIsHIDAPIEnabled(); public static native void nativeInitMainThread(); public static native void nativeCleanupMainThread(); public static native int nativeRunMain(String library, String function, Object arguments); @@ -1486,6 +1520,10 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh @Override public void run() { + if (mLayout == null) { + return; + } + RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(w, h + HEIGHT_PADDING); params.leftMargin = x; params.topMargin = y; @@ -1515,6 +1553,10 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh * This method is called by SDL using JNI. */ public static boolean showTextInput(int input_type, int x, int y, int w, int h) { + if (mLayout == null) { + return false; + } + // Transfer the task to the main thread as a Runnable return mSingleton.commandHandler.post(new ShowTextInputTask(input_type, x, y, w, h)); } @@ -1553,7 +1595,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh // Furthermore, it's possible a game controller has SOURCE_KEYBOARD and // SOURCE_JOYSTICK, while its key events arrive from the keyboard source // So, retrieve the device itself and check all of its sources - if (SDLControllerManager.isDeviceSDLJoystick(device)) { + if (SDL.isControllerManagerReady() && SDLControllerManager.isDeviceSDLJoystick(device)) { // Note that we process events with specific key codes here if (event.getAction() == KeyEvent.ACTION_DOWN) { if (SDLControllerManager.onNativePadDown(deviceId, keyCode, event.getScanCode())) { diff --git a/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java b/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java index 0e6c204945..e5b0b86460 100644 --- a/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java +++ b/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java @@ -60,7 +60,7 @@ public class SDLControllerManager mJoystickHandler = new SDLJoystickHandler(); } - if (mHapticHandler == null) { + if (mHapticHandler == null && SDL.isSubsystemCompiled(SDL.SDL_INIT_HAPTIC)) { if (Build.VERSION.SDK_INT >= 31 /* Android 12.0 (S) */) { mHapticHandler = new SDLHapticHandler_API31(); } else if (Build.VERSION.SDK_INT >= 26 /* Android 8.0 (O) */) { @@ -90,7 +90,7 @@ public class SDLControllerManager // Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance static public boolean handleJoystickMotionEvent(MotionEvent event) { - return mJoystickHandler.handleMotionEvent(event); + return mJoystickHandler != null && mJoystickHandler.handleMotionEvent(event); } /** @@ -118,21 +118,27 @@ public class SDLControllerManager * This method is called by SDL using JNI. */ static void detectHapticDevices() { - mHapticHandler.detectHapticDevices(); + if (mHapticHandler != null) { + mHapticHandler.detectHapticDevices(); + } } /** * This method is called by SDL using JNI. */ static void hapticRun(int device_id, float intensity, int length) { - mHapticHandler.run(device_id, intensity, length); + if (mHapticHandler != null) { + mHapticHandler.run(device_id, intensity, length); + } } /** * This method is called by SDL using JNI. */ static void hapticRumble(int device_id, float low_frequency_intensity, float high_frequency_intensity, int length) { - mHapticHandler.rumble(device_id, low_frequency_intensity, high_frequency_intensity, length); + if (mHapticHandler != null) { + mHapticHandler.rumble(device_id, low_frequency_intensity, high_frequency_intensity, length); + } } /** @@ -140,7 +146,9 @@ public class SDLControllerManager */ static void hapticStop(int device_id) { - mHapticHandler.stop(device_id); + if (mHapticHandler != null) { + mHapticHandler.stop(device_id); + } } // Check if a given device is considered a possible SDL joystick @@ -977,10 +985,13 @@ class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 { } if (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */) { - if (enabled) { - SDLActivity.getContentView().requestPointerCapture(); - } else { - SDLActivity.getContentView().releasePointerCapture(); + View contentView = SDLActivity.getContentView(); + if (contentView != null) { + if (enabled) { + contentView.requestPointerCapture(); + } else { + contentView.releasePointerCapture(); + } } mRelativeModeEnabled = enabled; return true; @@ -998,7 +1009,10 @@ class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 { } if (mRelativeModeEnabled && !SDLActivity.isDeXMode()) { - SDLActivity.getContentView().requestPointerCapture(); + View contentView = SDLActivity.getContentView(); + if (contentView != null) { + contentView.requestPointerCapture(); + } } } diff --git a/src/core/android/SDL_android.c b/src/core/android/SDL_android.c index fd95400d15..16322a7012 100644 --- a/src/core/android/SDL_android.c +++ b/src/core/android/SDL_android.c @@ -61,6 +61,116 @@ #define ENCODING_PCM_16BIT 2 #define ENCODING_PCM_FLOAT 4 +// The SDLControllerManager Java class backs both the joystick and haptic subsystems. +// Haptic requires joystick (enforced in CMake), so gating on joystick covers both. +#ifndef SDL_JOYSTICK_DISABLED +#define SDL_ANDROID_NEED_CONTROLLER_MANAGER +#endif + +static void checkJNIReady(void); + +/******************************************************************************* + This file links the Java side of Android with libsdl +*******************************************************************************/ +#include + +/******************************************************************************* + Globals +*******************************************************************************/ +static pthread_key_t mThreadKey; +static pthread_once_t key_once = PTHREAD_ONCE_INIT; +static JavaVM *mJavaVM = NULL; + +// Main activity +static jclass mActivityClass; + +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 midManualBackButton; +#ifndef SDL_DIALOG_DISABLED +static jmethodID midShowFileDialog; +#endif // !SDL_DIALOG_DISABLED +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 midMinimizeWindow; +static jmethodID midSetActivityTitle; +static jmethodID midSetCustomCursor; +static jmethodID midSetOrientation; +static jmethodID midSetRelativeMouseEnabled; +static jmethodID midSetSystemCursor; +static jmethodID midSetWindowStyle; +static jmethodID midShouldMinimizeOnFocusLoss; +static jmethodID midShowTextInput; +static jmethodID midSupportsRelativeMouse; +#endif // !SDL_VIDEO_DISABLED + +#ifndef SDL_AUDIO_DISABLED +// audio manager +static jclass mAudioManagerClass; + +// method signatures +static jmethodID midRegisterAudioDeviceCallback; +static jmethodID midUnregisterAudioDeviceCallback; +static jmethodID midAudioSetThreadPriority; +#endif // !SDL_AUDIO_DISABLED + +// controller manager +#ifdef SDL_ANDROID_NEED_CONTROLLER_MANAGER +static jclass mControllerManagerClass; + +// method signatures +static jmethodID midDetectDevices; +static jmethodID midJoystickSetLED; +static jmethodID midJoystickSetSensorsEnabled; +#endif // SDL_ANDROID_NEED_CONTROLLER_MANAGER + +#ifndef SDL_HAPTIC_DISABLED +static jmethodID midDetectHapticDevices; +static jmethodID midHapticRun; +static jmethodID midHapticRumble; +static jmethodID midHapticStop; +#endif // !SDL_HAPTIC_DISABLED + +#ifndef SDL_VIDEO_DISABLED +// display orientation +static SDL_DisplayOrientation displayNaturalOrientation; +static SDL_DisplayOrientation displayCurrentOrientation; +#endif // !SDL_VIDEO_DISABLED + +static bool bHasEnvironmentVariables; + +// Android AssetManager +static void Internal_Android_Create_AssetManager(void); +static void Internal_Android_Destroy_AssetManager(void); +static AAssetManager *asset_manager = NULL; +static jobject javaAssetManagerRef = 0; + +static SDL_Mutex *Android_ActivityMutex = NULL; +static int Android_ActivityMutexCount = 0; +static SDL_Mutex *Android_LifecycleMutex = NULL; +static SDL_Semaphore *Android_LifecycleEventSem = NULL; +static SDL_AndroidLifecycleEvent Android_LifecycleEvents[SDL_NUM_ANDROID_LIFECYCLE_EVENTS]; +static int Android_NumLifecycleEvents; + // Java class SDLActivity JNIEXPORT jstring JNICALL SDL_JAVA_INTERFACE(nativeGetVersion)( JNIEnv *env, jclass cls); @@ -68,6 +178,12 @@ JNIEXPORT jstring JNICALL SDL_JAVA_INTERFACE(nativeGetVersion)( JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)( JNIEnv *env, jclass cls); +JNIEXPORT jint JNICALL SDL_JAVA_INTERFACE(nativeGetCompiledSubsystems)( + JNIEnv *env, jclass jcls); + +JNIEXPORT jboolean JNICALL SDL_JAVA_INTERFACE(nativeIsHIDAPIEnabled)( + JNIEnv *env, jclass jcls); + JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeInitMainThread)( JNIEnv *env, jclass cls); @@ -91,6 +207,22 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetScreenResolution)( JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeResize)( JNIEnv *env, jclass cls); +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetNaturalOrientation)( + JNIEnv *env, jclass cls, + jint orientation); + +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeRotationChanged)( + JNIEnv *env, jclass cls, + jint rotation); + +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeInsetsChanged)( + JNIEnv *env, jclass cls, + jint left, jint right, jint top, jint bottom); + +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeAddTouch)( + JNIEnv *env, jclass cls, + jint touchId, jstring name); + JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceCreated)( JNIEnv *env, jclass jcls); @@ -147,6 +279,20 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePen)( JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeClipboardChanged)( JNIEnv *env, jclass jcls); + +// Java class SDLInputConnection +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeCommitText)( + JNIEnv *env, jclass cls, + jstring text, jint newCursorPosition); + +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeGenerateScancodeForUnichar)( + JNIEnv *env, jclass cls, + jchar chUnicode); + +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 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeLowMemory)( @@ -185,24 +331,6 @@ 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); - -JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeRotationChanged)( - JNIEnv *env, jclass cls, - jint rotation); - -JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeInsetsChanged)( - JNIEnv *env, jclass cls, - jint left, jint right, jint top, jint bottom); - -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, jint requestCode, jboolean result); @@ -213,13 +341,17 @@ JNIEXPORT jboolean JNICALL SDL_JAVA_INTERFACE(nativeAllowRecreateActivity)( JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeCheckSDLThreadCounter)( JNIEnv *env, jclass jcls); +#ifndef SDL_DIALOG_DISABLED JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeFileDialog)( JNIEnv *env, jclass jcls, jint requestCode, jobjectArray fileList, jint filter); +#endif // !SDL_DIALOG_DISABLED static JNINativeMethod SDLActivity_tab[] = { { "nativeGetVersion", "()Ljava/lang/String;", SDL_JAVA_INTERFACE(nativeGetVersion) }, { "nativeSetupJNI", "()V", SDL_JAVA_INTERFACE(nativeSetupJNI) }, + { "nativeGetCompiledSubsystems", "()I", SDL_JAVA_INTERFACE(nativeGetCompiledSubsystems) }, + { "nativeIsHIDAPIEnabled", "()Z", SDL_JAVA_INTERFACE(nativeIsHIDAPIEnabled) }, { "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) }, @@ -237,12 +369,18 @@ static JNINativeMethod SDLActivity_tab[] = { { "nativePermissionResult", "(IZ)V", SDL_JAVA_INTERFACE(nativePermissionResult) }, { "nativeAllowRecreateActivity", "()Z", SDL_JAVA_INTERFACE(nativeAllowRecreateActivity) }, { "nativeCheckSDLThreadCounter", "()I", SDL_JAVA_INTERFACE(nativeCheckSDLThreadCounter) }, +#ifndef SDL_DIALOG_DISABLED { "onNativeFileDialog", "(I[Ljava/lang/String;I)V", SDL_JAVA_INTERFACE(onNativeFileDialog) }, +#endif // !SDL_DIALOG_DISABLED #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) }, + { "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) }, { "onNativeSurfaceCreated", "()V", SDL_JAVA_INTERFACE(onNativeSurfaceCreated) }, { "onNativeSurfaceChanged", "()V", SDL_JAVA_INTERFACE(onNativeSurfaceChanged) }, { "onNativeSurfaceDestroyed", "()V", SDL_JAVA_INTERFACE(onNativeSurfaceDestroyed) }, @@ -258,30 +396,10 @@ static JNINativeMethod SDLActivity_tab[] = { { "onNativePinchEnd", "(FFFF)V", SDL_JAVA_INTERFACE(onNativePinchEnd) }, { "onNativeMouse", "(IIFFZ)V", SDL_JAVA_INTERFACE(onNativeMouse) }, { "onNativePen", "(IIIIFFF)V", SDL_JAVA_INTERFACE(onNativePen) }, - { "onNativeClipboardChanged", "()V", SDL_JAVA_INTERFACE(onNativeClipboardChanged) }, - { "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) } + { "onNativeClipboardChanged", "()V", SDL_JAVA_INTERFACE(onNativeClipboardChanged) } #endif // !SDL_VIDEO_DISABLED }; -#ifndef SDL_VIDEO_DISABLED -// Java class SDLInputConnection -JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeCommitText)( - JNIEnv *env, jclass cls, - jstring text, jint newCursorPosition); - -JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeGenerateScancodeForUnichar)( - JNIEnv *env, jclass cls, - jchar chUnicode); - -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)( @@ -303,6 +421,7 @@ static JNINativeMethod SDLAudioManager_tab[] = { #endif // !SDL_AUDIO_DISABLED // Java class SDLControllerManager +#ifdef SDL_ANDROID_NEED_CONTROLLER_MANAGER JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI)( JNIEnv *env, jclass jcls); @@ -335,7 +454,9 @@ JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick)( JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveJoystick)( JNIEnv *env, jclass jcls, jint device_id); +#endif // SDL_ANDROID_NEED_CONTROLLER_MANAGER +#ifndef SDL_HAPTIC_DISABLED JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddHaptic)( JNIEnv *env, jclass jcls, jint device_id, jstring device_name); @@ -343,7 +464,9 @@ JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddHaptic)( JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveHaptic)( JNIEnv *env, jclass jcls, jint device_id); +#endif // !SDL_HAPTIC_DISABLED +#ifdef SDL_ANDROID_NEED_CONTROLLER_MANAGER static JNINativeMethod SDLControllerManager_tab[] = { { "nativeSetupJNI", "()V", SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI) }, { "onNativePadDown", "(III)Z", SDL_JAVA_CONTROLLER_INTERFACE(onNativePadDown) }, @@ -353,107 +476,16 @@ static JNINativeMethod SDLControllerManager_tab[] = { { "onNativeJoySensor", "(IIJFFF)V", SDL_JAVA_CONTROLLER_INTERFACE(onNativeJoySensor) }, { "nativeAddJoystick", "(ILjava/lang/String;Ljava/lang/String;IIIIIIZZZZ)V", SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick) }, { "nativeRemoveJoystick", "(I)V", SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveJoystick) }, +#ifndef SDL_HAPTIC_DISABLED { "nativeAddHaptic", "(ILjava/lang/String;)V", SDL_JAVA_CONTROLLER_INTERFACE(nativeAddHaptic) }, { "nativeRemoveHaptic", "(I)V", SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveHaptic) } +#endif // !SDL_HAPTIC_DISABLED }; +#endif // SDL_ANDROID_NEED_CONTROLLER_MANAGER // Uncomment this to log messages entering and exiting methods in this file // #define DEBUG_JNI -static void checkJNIReady(void); - -/******************************************************************************* - This file links the Java side of Android with libsdl -*******************************************************************************/ -#include - -/******************************************************************************* - Globals -*******************************************************************************/ -static pthread_key_t mThreadKey; -static pthread_once_t key_once = PTHREAD_ONCE_INIT; -static JavaVM *mJavaVM = NULL; - -// Main activity -static jclass mActivityClass; - -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 midGetNativeSurface; -static jmethodID midInitTouch; -static jmethodID midManualBackButton; -static jmethodID midMinimizeWindow; -static jmethodID midSetActivityTitle; -static jmethodID midSetCustomCursor; -static jmethodID midSetOrientation; -static jmethodID midSetRelativeMouseEnabled; -static jmethodID midSetSystemCursor; -static jmethodID midSetWindowStyle; -static jmethodID midShouldMinimizeOnFocusLoss; -static jmethodID midShowTextInput; -static jmethodID midSupportsRelativeMouse; -#endif // !SDL_VIDEO_DISABLED - -#ifndef SDL_AUDIO_DISABLED -// audio manager -static jclass mAudioManagerClass; - -// method signatures -static jmethodID midRegisterAudioDeviceCallback; -static jmethodID midUnregisterAudioDeviceCallback; -static jmethodID midAudioSetThreadPriority; -#endif // !SDL_AUDIO_DISABLED - -// controller manager -static jclass mControllerManagerClass; - -// method signatures -static jmethodID midDetectDevices; -static jmethodID midJoystickSetLED; -static jmethodID midJoystickSetSensorsEnabled; -static jmethodID midDetectHapticDevices; -static jmethodID midHapticRun; -static jmethodID midHapticRumble; -static jmethodID midHapticStop; - -// display orientation -static SDL_DisplayOrientation displayNaturalOrientation; -static SDL_DisplayOrientation displayCurrentOrientation; - -static bool bHasEnvironmentVariables; - -// Android AssetManager -static void Internal_Android_Create_AssetManager(void); -static void Internal_Android_Destroy_AssetManager(void); -static AAssetManager *asset_manager = NULL; -static jobject javaAssetManagerRef = 0; - -static SDL_Mutex *Android_ActivityMutex = NULL; -static int Android_ActivityMutexCount = 0; -static SDL_Mutex *Android_LifecycleMutex = NULL; -static SDL_Semaphore *Android_LifecycleEventSem = NULL; -static SDL_AndroidLifecycleEvent Android_LifecycleEvents[SDL_NUM_ANDROID_LIFECYCLE_EVENTS]; -static int Android_NumLifecycleEvents; /******************************************************************************* Functions called by JNI @@ -492,65 +524,6 @@ static bool Android_JNI_SetEnv(JNIEnv *env) return true; } -// Get local storage value -JNIEnv *Android_JNI_GetEnv(void) -{ - // Get JNIEnv from the Thread local storage - JNIEnv *env = pthread_getspecific(mThreadKey); - if (!env) { - // If it fails, try to attach ! (e.g the thread isn't created with SDL_CreateThread() - int status; - - // There should be a JVM - if (!mJavaVM) { - __android_log_print(ANDROID_LOG_ERROR, "SDL", "Failed, there is no JavaVM"); - return NULL; - } - - /* Attach the current thread to the JVM and get a JNIEnv. - * It will be detached by pthread_create destructor 'Android_JNI_ThreadDestroyed' */ - status = (*mJavaVM)->AttachCurrentThread(mJavaVM, &env, NULL); - if (status < 0) { - __android_log_print(ANDROID_LOG_ERROR, "SDL", "Failed to attach current thread (err=%d)", status); - return NULL; - } - - // Save JNIEnv into the Thread local storage - if (!Android_JNI_SetEnv(env)) { - return NULL; - } - } - - return env; -} - -// Set up an external thread for using JNI with Android_JNI_GetEnv() -bool Android_JNI_SetupThread(void) -{ - JNIEnv *env; - int status; - - // There should be a JVM - if (!mJavaVM) { - __android_log_print(ANDROID_LOG_ERROR, "SDL", "Failed, there is no JavaVM"); - return false; - } - - /* Attach the current thread to the JVM and get a JNIEnv. - * It will be detached by pthread_create destructor 'Android_JNI_ThreadDestroyed' */ - status = (*mJavaVM)->AttachCurrentThread(mJavaVM, &env, NULL); - if (status < 0) { - __android_log_print(ANDROID_LOG_ERROR, "SDL", "Failed to attach current thread (err=%d)", status); - return false; - } - - // Save JNIEnv into the Thread local storage - if (!Android_JNI_SetEnv(env)) { - return false; - } - - return true; -} // Destructor called for each thread where mThreadKey is not NULL static void Android_JNI_ThreadDestroyed(void *value) @@ -608,7 +581,9 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) #ifndef SDL_AUDIO_DISABLED register_methods(env, "org/libsdl/app/SDLAudioManager", SDLAudioManager_tab, SDL_arraysize(SDLAudioManager_tab)); #endif +#ifdef SDL_ANDROID_NEED_CONTROLLER_MANAGER register_methods(env, "org/libsdl/app/SDLControllerManager", SDLControllerManager_tab, SDL_arraysize(SDLControllerManager_tab)); +#endif register_methods(env, "org/libsdl/app/HIDDeviceManager", HIDDeviceManager_tab, SDL_arraysize(HIDDeviceManager_tab)); return JNI_VERSION_1_4; @@ -616,11 +591,17 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) void checkJNIReady(void) { - if (!mActivityClass || !mControllerManagerClass) { + if (!mActivityClass) { // We aren't fully initialized, let's just return. return; } +#ifdef SDL_ANDROID_NEED_CONTROLLER_MANAGER + if (!mControllerManagerClass) { + return; + } +#endif + #ifndef SDL_AUDIO_DISABLED if (!mAudioManagerClass) { return; @@ -696,7 +677,10 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cl 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"); + midManualBackButton = (*env)->GetStaticMethodID(env, mActivityClass, "manualBackButton", "()V"); +#ifndef SDL_DIALOG_DISABLED midShowFileDialog = (*env)->GetStaticMethodID(env, mActivityClass, "showFileDialog", "([Ljava/lang/String;ZILjava/lang/String;I)Z"); +#endif // !SDL_DIALOG_DISABLED midGetPreferredLocales = (*env)->GetStaticMethodID(env, mActivityClass, "getPreferredLocales", "()Ljava/lang/String;"); if (!midGetContext || @@ -711,7 +695,10 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cl !midShowToast || !midSendMessage || !midOpenFileDescriptor || + !midManualBackButton || +#ifndef SDL_DIALOG_DISABLED !midShowFileDialog || +#endif !midGetPreferredLocales) { __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some core Java callbacks, do you have the latest version of SDLActivity.java?"); } @@ -724,7 +711,6 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cl 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"); @@ -743,7 +729,6 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cl !midDestroyCustomCursor || !midGetNativeSurface || !midInitTouch || - !midManualBackButton || !midMinimizeWindow || !midSetActivityTitle || !midSetCustomCursor || @@ -761,7 +746,42 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cl checkJNIReady(); } +JNIEXPORT jint JNICALL SDL_JAVA_INTERFACE(nativeGetCompiledSubsystems)(JNIEnv *env, jclass jcls) +{ + Uint32 subsystems = SDL_INIT_AUDIO | SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMEPAD | + SDL_INIT_SENSOR | SDL_INIT_CAMERA; +#ifdef SDL_AUDIO_DISABLED + subsystems &= ~SDL_INIT_AUDIO; +#endif +#ifdef SDL_VIDEO_DISABLED + subsystems &= ~SDL_INIT_VIDEO; +#endif +#ifdef SDL_JOYSTICK_DISABLED + subsystems &= ~(SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD); +#endif +#ifdef SDL_HAPTIC_DISABLED + subsystems &= ~SDL_INIT_HAPTIC; +#endif +#ifdef SDL_SENSOR_DISABLED + subsystems &= ~SDL_INIT_SENSOR; +#endif +#ifdef SDL_CAMERA_DISABLED + subsystems &= ~SDL_INIT_CAMERA; +#endif + return (jint)subsystems; +} + +JNIEXPORT jboolean JNICALL SDL_JAVA_INTERFACE(nativeIsHIDAPIEnabled)(JNIEnv *env, jclass jcls) +{ +#ifdef SDL_HIDAPI_DISABLED + return JNI_FALSE; +#else + return JNI_TRUE; +#endif +} + #ifndef SDL_AUDIO_DISABLED +// Audio support // Audio initialization -- called before SDL_main() to initialize JNI bindings JNIEXPORT void JNICALL SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cls) { @@ -785,8 +805,40 @@ JNIEXPORT void JNICALL SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI)(JNIEnv *env, jcl checkJNIReady(); } + +JNIEXPORT void JNICALL SDL_JAVA_AUDIO_INTERFACE(nativeAddAudioDevice)(JNIEnv *env, jclass jcls, jboolean recording, jstring name, jint device_id) +{ +#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES + if (SDL_GetCurrentAudioDriver() != NULL) { + void *handle = (void *)((size_t)device_id); + if (!SDL_FindPhysicalAudioDeviceByHandle(handle)) { + const char *utf8name = (*env)->GetStringUTFChars(env, name, NULL); + SDL_AddAudioDevice(recording, SDL_strdup(utf8name), NULL, handle); + (*env)->ReleaseStringUTFChars(env, name, utf8name); + } + } +#endif +} + +JNIEXPORT void JNICALL SDL_JAVA_AUDIO_INTERFACE(nativeRemoveAudioDevice)(JNIEnv *env, jclass jcls, jboolean recording, jint device_id) +{ +#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES + if (SDL_GetCurrentAudioDriver() != NULL) { + SDL_Log("Removing device with handle %d, recording %d", device_id, recording); + SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByHandle((void *)((size_t)device_id))); + } +#endif +} + +static void Android_JNI_AudioSetThreadPriority(int recording, int device_id) +{ + JNIEnv *env = Android_JNI_GetEnv(); + (*env)->CallStaticVoidMethod(env, mAudioManagerClass, midAudioSetThreadPriority, recording, device_id); +} + #endif // !SDL_AUDIO_DISABLED +#ifdef SDL_ANDROID_NEED_CONTROLLER_MANAGER // Controller initialization -- called before SDL_main() to initialize JNI bindings JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cls) { @@ -800,6 +852,7 @@ JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI)(JNIEnv *env "joystickSetLED", "(IIII)V"); midJoystickSetSensorsEnabled = (*env)->GetStaticMethodID(env, mControllerManagerClass, "joystickSetSensorsEnabled", "(IZ)V"); +#ifndef SDL_HAPTIC_DISABLED midDetectHapticDevices = (*env)->GetStaticMethodID(env, mControllerManagerClass, "detectHapticDevices", "()V"); midHapticRun = (*env)->GetStaticMethodID(env, mControllerManagerClass, @@ -808,13 +861,19 @@ JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI)(JNIEnv *env "hapticRumble", "(IFFI)V"); midHapticStop = (*env)->GetStaticMethodID(env, mControllerManagerClass, "hapticStop", "(I)V"); +#endif // !SDL_HAPTIC_DISABLED - if (!midDetectDevices || !midJoystickSetLED || !midJoystickSetSensorsEnabled || !midDetectHapticDevices || !midHapticRun || !midHapticRumble || !midHapticStop) { + if (!midDetectDevices || !midJoystickSetLED || !midJoystickSetSensorsEnabled +#ifndef SDL_HAPTIC_DISABLED + || !midDetectHapticDevices || !midHapticRun || !midHapticRumble || !midHapticStop +#endif + ) { __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLControllerManager.java?"); } checkJNIReady(); } +#endif // SDL_ANDROID_NEED_CONTROLLER_MANAGER static int run_count = 0; static bool allow_recreate_activity; @@ -828,11 +887,6 @@ JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeCheckSDLThreadCounter)( return tmp; } -void Android_SetAllowRecreateActivity(bool enabled) -{ - allow_recreate_activity = enabled; - allow_recreate_activity_set = true; -} JNIEXPORT jboolean JNICALL SDL_JAVA_INTERFACE(nativeAllowRecreateActivity)( JNIEnv *env, jclass jcls) @@ -976,109 +1030,112 @@ static void RemoveLifecycleEvent(int index) --Android_NumLifecycleEvents; } -void Android_SendLifecycleEvent(SDL_AndroidLifecycleEvent event) +#ifdef SDL_ANDROID_NEED_CONTROLLER_MANAGER +// Paddown +JNIEXPORT jboolean JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadDown)( + JNIEnv *env, jclass jcls, + jint device_id, jint keycode, jint scancode) { - SDL_LockMutex(Android_LifecycleMutex); - { - int index; - bool add_event = true; - - switch (event) { - case SDL_ANDROID_LIFECYCLE_WAKE: - // We don't need more than one wake queued - index = FindLifecycleEvent(SDL_ANDROID_LIFECYCLE_WAKE); - if (index >= 0) { - add_event = false; - } - break; - case SDL_ANDROID_LIFECYCLE_PAUSE: - // If we have a resume queued, just stay in the paused state - index = FindLifecycleEvent(SDL_ANDROID_LIFECYCLE_RESUME); - if (index >= 0) { - RemoveLifecycleEvent(index); - add_event = false; - } - break; - case SDL_ANDROID_LIFECYCLE_RESUME: - // If we have a pause queued, just stay in the resumed state - index = FindLifecycleEvent(SDL_ANDROID_LIFECYCLE_PAUSE); - if (index >= 0) { - RemoveLifecycleEvent(index); - add_event = false; - } - break; - case SDL_ANDROID_LIFECYCLE_LOWMEMORY: - // We don't need more than one low memory event queued - index = FindLifecycleEvent(SDL_ANDROID_LIFECYCLE_LOWMEMORY); - if (index >= 0) { - add_event = false; - } - break; - case SDL_ANDROID_LIFECYCLE_DESTROY: - // Remove all other events, we're done! - while (Android_NumLifecycleEvents > 0) { - RemoveLifecycleEvent(0); - } - break; - default: - SDL_assert(!"Sending unexpected lifecycle event"); - add_event = false; - break; - } - - if (add_event) { - SDL_assert(Android_NumLifecycleEvents < SDL_arraysize(Android_LifecycleEvents)); - Android_LifecycleEvents[Android_NumLifecycleEvents++] = event; - SDL_SignalSemaphore(Android_LifecycleEventSem); - } - } - SDL_UnlockMutex(Android_LifecycleMutex); +#ifdef SDL_JOYSTICK_ANDROID + return Android_OnPadDown(device_id, keycode, scancode); +#else + return false; +#endif // SDL_JOYSTICK_ANDROID } -bool Android_WaitLifecycleEvent(SDL_AndroidLifecycleEvent *event, Sint64 timeoutNS) +// Padup +JNIEXPORT jboolean JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadUp)( + JNIEnv *env, jclass jcls, + jint device_id, jint keycode, jint scancode) { - bool got_event = false; - - int relock_count = 0; - Android_LockActivityMutex(); - while (Android_ActivityMutexCount > 1) { - // We came into this function with the activity lock held, we need to unlock so lifecycle events can be dispatched - ++relock_count; - Android_UnlockActivityMutex(); - } - Android_UnlockActivityMutex(); - - while (!got_event && SDL_WaitSemaphoreTimeoutNS(Android_LifecycleEventSem, timeoutNS)) { - SDL_LockMutex(Android_LifecycleMutex); - { - if (Android_NumLifecycleEvents > 0) { - *event = Android_LifecycleEvents[0]; - RemoveLifecycleEvent(0); - got_event = true; - } - } - SDL_UnlockMutex(Android_LifecycleMutex); - } - - while (relock_count > 0) { - Android_LockActivityMutex(); - --relock_count; - } - return got_event; +#ifdef SDL_JOYSTICK_ANDROID + return Android_OnPadUp(device_id, keycode, scancode); +#else + return false; +#endif // SDL_JOYSTICK_ANDROID } -void Android_LockActivityMutex(void) +// Joy +JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeJoy)( + JNIEnv *env, jclass jcls, + jint device_id, jint axis, jfloat value) { - SDL_LockMutex(Android_ActivityMutex); - ++Android_ActivityMutexCount; +#ifdef SDL_JOYSTICK_ANDROID + Android_OnJoy(device_id, axis, value); +#endif // SDL_JOYSTICK_ANDROID } -void Android_UnlockActivityMutex(void) +// POV Hat +JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeHat)( + JNIEnv *env, jclass jcls, + jint device_id, jint hat_id, jint x, jint y) { - --Android_ActivityMutexCount; - SDL_UnlockMutex(Android_ActivityMutex); +#ifdef SDL_JOYSTICK_ANDROID + Android_OnHat(device_id, hat_id, x, y); +#endif // SDL_JOYSTICK_ANDROID } +JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeJoySensor)( + JNIEnv *env, jclass jcls, + jint device_id, jint sensor_type, jlong sensor_timestamp, jfloat x, jfloat y, jfloat z) +{ +#ifdef SDL_JOYSTICK_ANDROID + // In Java there's no Uint64 type, so pass Sint64 as if it was Uint64. + Android_OnJoySensor(device_id, sensor_type, sensor_timestamp, x, y, z); +#endif // SDL_JOYSTICK_ANDROID +} + +JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick)( + JNIEnv *env, jclass jcls, + jint device_id, jstring device_name, jstring device_desc, + jint vendor_id, jint product_id, + jint button_mask, jint naxes, jint axis_mask, jint nhats, jboolean can_rumble, jboolean has_rgb_led, + jboolean has_accelerometer, jboolean has_gyroscope) +{ +#ifdef SDL_JOYSTICK_ANDROID + const char *name = (*env)->GetStringUTFChars(env, device_name, NULL); + const char *desc = (*env)->GetStringUTFChars(env, device_desc, NULL); + + Android_AddJoystick(device_id, name, desc, vendor_id, product_id, button_mask, naxes, axis_mask, nhats, + can_rumble, has_rgb_led, has_accelerometer, has_gyroscope); + + (*env)->ReleaseStringUTFChars(env, device_name, name); + (*env)->ReleaseStringUTFChars(env, device_desc, desc); +#endif // SDL_JOYSTICK_ANDROID +} + +JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveJoystick)( + JNIEnv *env, jclass jcls, + jint device_id) +{ +#ifdef SDL_JOYSTICK_ANDROID + Android_RemoveJoystick(device_id); +#endif // SDL_JOYSTICK_ANDROID +} +#endif // SDL_ANDROID_NEED_CONTROLLER_MANAGER + +#ifndef SDL_HAPTIC_DISABLED +JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddHaptic)( + JNIEnv *env, jclass jcls, jint device_id, jstring device_name) +{ +#ifdef SDL_HAPTIC_ANDROID + const char *name = (*env)->GetStringUTFChars(env, device_name, NULL); + + Android_AddHaptic(device_id, name); + + (*env)->ReleaseStringUTFChars(env, device_name, name); +#endif // SDL_HAPTIC_ANDROID +} + +JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveHaptic)( + JNIEnv *env, jclass jcls, jint device_id) +{ +#ifdef SDL_HAPTIC_ANDROID + Android_RemoveHaptic(device_id); +#endif +} +#endif // !SDL_HAPTIC_DISABLED + #ifndef SDL_VIDEO_DISABLED // Drop file JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDropFile)( @@ -1179,141 +1236,7 @@ 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) -{ -#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES - if (SDL_GetCurrentAudioDriver() != NULL) { - void *handle = (void *)((size_t)device_id); - if (!SDL_FindPhysicalAudioDeviceByHandle(handle)) { - const char *utf8name = (*env)->GetStringUTFChars(env, name, NULL); - SDL_AddAudioDevice(recording, SDL_strdup(utf8name), NULL, handle); - (*env)->ReleaseStringUTFChars(env, name, utf8name); - } - } -#endif -} - -JNIEXPORT void JNICALL -SDL_JAVA_AUDIO_INTERFACE(nativeRemoveAudioDevice)(JNIEnv *env, jclass jcls, jboolean recording, - jint device_id) -{ -#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES - if (SDL_GetCurrentAudioDriver() != NULL) { - SDL_Log("Removing device with handle %d, recording %d", device_id, recording); - SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByHandle((void *)((size_t)device_id))); - } -#endif -} -#endif // !SDL_AUDIO_DISABLED - -// Paddown -JNIEXPORT jboolean JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadDown)( - JNIEnv *env, jclass jcls, - jint device_id, jint keycode, jint scancode) -{ -#ifdef SDL_JOYSTICK_ANDROID - return Android_OnPadDown(device_id, keycode, scancode); -#else - return false; -#endif // SDL_JOYSTICK_ANDROID -} - -// Padup -JNIEXPORT jboolean JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadUp)( - JNIEnv *env, jclass jcls, - jint device_id, jint keycode, jint scancode) -{ -#ifdef SDL_JOYSTICK_ANDROID - return Android_OnPadUp(device_id, keycode, scancode); -#else - return false; -#endif // SDL_JOYSTICK_ANDROID -} - -// Joy -JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeJoy)( - JNIEnv *env, jclass jcls, - jint device_id, jint axis, jfloat value) -{ -#ifdef SDL_JOYSTICK_ANDROID - Android_OnJoy(device_id, axis, value); -#endif // SDL_JOYSTICK_ANDROID -} - -// POV Hat -JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeHat)( - JNIEnv *env, jclass jcls, - jint device_id, jint hat_id, jint x, jint y) -{ -#ifdef SDL_JOYSTICK_ANDROID - Android_OnHat(device_id, hat_id, x, y); -#endif // SDL_JOYSTICK_ANDROID -} - -JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeJoySensor)( - JNIEnv *env, jclass jcls, - jint device_id, jint sensor_type, jlong sensor_timestamp, jfloat x, jfloat y, jfloat z) -{ -#ifdef SDL_JOYSTICK_ANDROID - // In Java there's no Uint64 type, so pass Sint64 as if it was Uint64. - Android_OnJoySensor(device_id, sensor_type, sensor_timestamp, x, y, z); -#endif // SDL_JOYSTICK_ANDROID -} - -JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick)( - JNIEnv *env, jclass jcls, - jint device_id, jstring device_name, jstring device_desc, - jint vendor_id, jint product_id, - jint button_mask, jint naxes, jint axis_mask, jint nhats, jboolean can_rumble, jboolean has_rgb_led, - jboolean has_accelerometer, jboolean has_gyroscope) -{ -#ifdef SDL_JOYSTICK_ANDROID - const char *name = (*env)->GetStringUTFChars(env, device_name, NULL); - const char *desc = (*env)->GetStringUTFChars(env, device_desc, NULL); - - Android_AddJoystick(device_id, name, desc, vendor_id, product_id, button_mask, naxes, axis_mask, nhats, - can_rumble, has_rgb_led, has_accelerometer, has_gyroscope); - - (*env)->ReleaseStringUTFChars(env, device_name, name); - (*env)->ReleaseStringUTFChars(env, device_desc, desc); -#endif // SDL_JOYSTICK_ANDROID -} - -JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveJoystick)( - JNIEnv *env, jclass jcls, - jint device_id) -{ -#ifdef SDL_JOYSTICK_ANDROID - Android_RemoveJoystick(device_id); -#endif // SDL_JOYSTICK_ANDROID -} - -JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddHaptic)( - JNIEnv *env, jclass jcls, jint device_id, jstring device_name) -{ -#ifdef SDL_HAPTIC_ANDROID - const char *name = (*env)->GetStringUTFChars(env, device_name, NULL); - - Android_AddHaptic(device_id, name); - - (*env)->ReleaseStringUTFChars(env, device_name, name); -#endif // SDL_HAPTIC_ANDROID -} - -JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveHaptic)( - JNIEnv *env, jclass jcls, jint device_id) -{ -#ifdef SDL_HAPTIC_ANDROID - Android_RemoveHaptic(device_id); -#endif -} - -#ifndef SDL_VIDEO_DISABLED // Called from surfaceCreated() JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceCreated)(JNIEnv *env, jclass jcls) { @@ -1755,109 +1678,19 @@ static void LocalReferenceHolder_Cleanup(struct LocalReferenceHolder *refholder) } } -#ifndef SDL_VIDEO_DISABLED -ANativeWindow *Android_JNI_GetNativeWindow(void) -{ - ANativeWindow *anw = NULL; - jobject s; - JNIEnv *env = Android_JNI_GetEnv(); - - s = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetNativeSurface); - if (s) { - anw = ANativeWindow_fromSurface(env, s); - (*env)->DeleteLocalRef(env, s); - } - - return anw; -} - -void Android_JNI_SetActivityTitle(const char *title) -{ - JNIEnv *env = Android_JNI_GetEnv(); - - jstring jtitle = (*env)->NewStringUTF(env, title); - (*env)->CallStaticBooleanMethod(env, mActivityClass, midSetActivityTitle, jtitle); - (*env)->DeleteLocalRef(env, jtitle); -} - -void Android_JNI_SetWindowStyle(bool fullscreen) -{ - JNIEnv *env = Android_JNI_GetEnv(); - (*env)->CallStaticVoidMethod(env, mActivityClass, midSetWindowStyle, fullscreen ? 1 : 0); -} - -void Android_JNI_SetOrientation(int w, int h, int resizable, const char *hint) -{ - JNIEnv *env = Android_JNI_GetEnv(); - - jstring jhint = (*env)->NewStringUTF(env, (hint ? hint : "")); - (*env)->CallStaticVoidMethod(env, mActivityClass, midSetOrientation, w, h, (resizable ? 1 : 0), jhint); - (*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; -} - -SDL_DisplayOrientation Android_JNI_GetDisplayCurrentOrientation(void) -{ - return displayCurrentOrientation; -} - -#ifndef SDL_VIDEO_DISABLED -void Android_JNI_MinimizeWindow(void) -{ - JNIEnv *env = Android_JNI_GetEnv(); - (*env)->CallStaticVoidMethod(env, mActivityClass, midMinimizeWindow); -} - -bool Android_JNI_ShouldMinimizeOnFocusLoss(void) -{ - JNIEnv *env = Android_JNI_GetEnv(); - return (*env)->CallStaticBooleanMethod(env, mActivityClass, midShouldMinimizeOnFocusLoss); -} - -#else +#ifdef SDL_VIDEO_DISABLED bool Android_JNI_ShouldMinimizeOnFocusLoss(void) { return false; } -#endif // !SDL_VIDEO_DISABLED +#endif // SDL_VIDEO_DISABLED -#ifndef SDL_AUDIO_DISABLED -/* - * Audio support - */ -void Android_StartAudioHotplug(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording) +void SDL_SendAndroidBackButton(void) { JNIEnv *env = Android_JNI_GetEnv(); - // this will fire the callback for each existing device right away (which will eventually SDL_AddAudioDevice), and again later when things change. - (*env)->CallStaticVoidMethod(env, mAudioManagerClass, midRegisterAudioDeviceCallback); - *default_playback = *default_recording = NULL; // !!! FIXME: how do you decide the default device id? + (*env)->CallStaticVoidMethod(env, mActivityClass, midManualBackButton); } -void Android_StopAudioHotplug(void) -{ - JNIEnv *env = Android_JNI_GetEnv(); - (*env)->CallStaticVoidMethod(env, mAudioManagerClass, midUnregisterAudioDeviceCallback); -} - -static void Android_JNI_AudioSetThreadPriority(int recording, int device_id) -{ - JNIEnv *env = Android_JNI_GetEnv(); - (*env)->CallStaticVoidMethod(env, mAudioManagerClass, midAudioSetThreadPriority, recording, device_id); -} - -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. static bool Android_JNI_ExceptionOccurred(bool silent) @@ -2461,316 +2294,6 @@ static const char *GetAssetPath(const char *path) return path; } -bool Android_JNI_FileOpen(void **puserdata, const char *fileName, const char *mode) -{ - SDL_assert(puserdata != NULL); - - AAsset *asset = NULL; - *puserdata = NULL; - - if (!asset_manager) { - Internal_Android_Create_AssetManager(); - if (!asset_manager) { - return SDL_SetError("Couldn't create asset manager"); - } - } - - fileName = GetAssetPath(fileName); - - asset = AAssetManager_open(asset_manager, fileName, AASSET_MODE_UNKNOWN); - if (!asset) { - return SDL_SetError("Couldn't open asset '%s'", fileName); - } - - *puserdata = (void *)asset; - return true; -} - -size_t Android_JNI_FileRead(void *userdata, void *buffer, size_t size, SDL_IOStatus *status) -{ - const int bytes = AAsset_read((AAsset *)userdata, buffer, size); - if (bytes < 0) { - SDL_SetError("AAsset_read() failed"); - *status = SDL_IO_STATUS_ERROR; - return 0; - } else if (bytes < size) { - *status = SDL_IO_STATUS_EOF; - } - return (size_t)bytes; -} - -size_t Android_JNI_FileWrite(void *userdata, const void *buffer, size_t size, SDL_IOStatus *status) -{ - SDL_SetError("Cannot write to Android package filesystem"); - *status = SDL_IO_STATUS_ERROR; - return 0; -} - -Sint64 Android_JNI_FileSize(void *userdata) -{ - return (Sint64) AAsset_getLength64((AAsset *)userdata); -} - -Sint64 Android_JNI_FileSeek(void *userdata, Sint64 offset, SDL_IOWhence whence) -{ - return (Sint64) AAsset_seek64((AAsset *)userdata, offset, (int)whence); -} - -bool Android_JNI_FileClose(void *userdata) -{ - AAsset_close((AAsset *)userdata); - return true; -} - -bool Android_JNI_EnumerateAssetDirectory(const char *path, SDL_EnumerateDirectoryCallback cb, void *userdata) -{ - SDL_assert(path != NULL); - - if (!PrepareAPK()) { - return false; - } - - SDL_EnumerationResult result = SDL_ENUM_CONTINUE; - const char *asset_path = GetAssetPath(path); - - const APKNode *apknode = FindAPKNode(asset_path); - if (!apknode) { - return SDL_SetError("No such directory"); - } else if (apknode->info.type != SDL_PATHTYPE_DIRECTORY) { - return SDL_SetError("Not a directory"); - } else { - for (const APKNode *node = apknode->children; node && (result == SDL_ENUM_CONTINUE); node = node->next_sibling) { - result = cb(userdata, path, node->name); - } - } - - return (result != SDL_ENUM_FAILURE); -} - -bool Android_JNI_GetAssetPathInfo(const char *path, SDL_PathInfo *info) -{ - SDL_assert(path != NULL); - - if (!PrepareAPK()) { - return false; - } - - path = GetAssetPath(path); - const APKNode *apknode = FindAPKNode(path); - if (!apknode) { - return SDL_SetError("No such file or directory"); - } - SDL_copyp(info, &apknode->info); - return true; -} - -#ifndef SDL_VIDEO_DISABLED -bool Android_JNI_SetClipboardText(const char *text) -{ - JNIEnv *env = Android_JNI_GetEnv(); - jstring string = (*env)->NewStringUTF(env, text); - (*env)->CallStaticVoidMethod(env, mActivityClass, midClipboardSetText, string); - (*env)->DeleteLocalRef(env, string); - return true; -} - -char *Android_JNI_GetClipboardText(void) -{ - JNIEnv *env = Android_JNI_GetEnv(); - char *text = NULL; - jstring string; - - string = (*env)->CallStaticObjectMethod(env, mActivityClass, midClipboardGetText); - if (string) { - const char *utf = (*env)->GetStringUTFChars(env, string, 0); - if (utf) { - text = SDL_strdup(utf); - (*env)->ReleaseStringUTFChars(env, string, utf); - } - (*env)->DeleteLocalRef(env, string); - } - - return (!text) ? SDL_strdup("") : text; -} - -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 - * returns the value in seconds and percent or -1 if not available - */ -int Android_JNI_GetPowerInfo(int *plugged, int *charged, int *battery, int *seconds, int *percent) -{ - struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(SDL_FUNCTION); - JNIEnv *env = Android_JNI_GetEnv(); - jmethodID mid; - jobject context; - jstring action; - jclass cls; - jobject filter; - jobject intent; - jstring iname; - jmethodID imid; - jstring bname; - jmethodID bmid; - if (!LocalReferenceHolder_Init(&refs, env)) { - LocalReferenceHolder_Cleanup(&refs); - return -1; - } - - // context = SDLActivity.getContext(); - context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext); - - action = (*env)->NewStringUTF(env, "android.intent.action.BATTERY_CHANGED"); - - cls = (*env)->FindClass(env, "android/content/IntentFilter"); - - mid = (*env)->GetMethodID(env, cls, "", "(Ljava/lang/String;)V"); - filter = (*env)->NewObject(env, cls, mid, action); - - (*env)->DeleteLocalRef(env, action); - - mid = (*env)->GetMethodID(env, mActivityClass, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;"); - intent = (*env)->CallObjectMethod(env, context, mid, NULL, filter); - - (*env)->DeleteLocalRef(env, filter); - - cls = (*env)->GetObjectClass(env, intent); - - imid = (*env)->GetMethodID(env, cls, "getIntExtra", "(Ljava/lang/String;I)I"); - - // Watch out for C89 scoping rules because of the macro -#define GET_INT_EXTRA(var, key) \ - int var; \ - iname = (*env)->NewStringUTF(env, key); \ - (var) = (*env)->CallIntMethod(env, intent, imid, iname, -1); \ - (*env)->DeleteLocalRef(env, iname); - - bmid = (*env)->GetMethodID(env, cls, "getBooleanExtra", "(Ljava/lang/String;Z)Z"); - - // Watch out for C89 scoping rules because of the macro -#define GET_BOOL_EXTRA(var, key) \ - int var; \ - bname = (*env)->NewStringUTF(env, key); \ - (var) = (*env)->CallBooleanMethod(env, intent, bmid, bname, JNI_FALSE); \ - (*env)->DeleteLocalRef(env, bname); - - if (plugged) { - // Watch out for C89 scoping rules because of the macro - GET_INT_EXTRA(plug, "plugged") // == BatteryManager.EXTRA_PLUGGED (API 5) - if (plug == -1) { - LocalReferenceHolder_Cleanup(&refs); - return -1; - } - // 1 == BatteryManager.BATTERY_PLUGGED_AC - // 2 == BatteryManager.BATTERY_PLUGGED_USB - *plugged = (0 < plug) ? 1 : 0; - } - - if (charged) { - // Watch out for C89 scoping rules because of the macro - GET_INT_EXTRA(status, "status") // == BatteryManager.EXTRA_STATUS (API 5) - if (status == -1) { - LocalReferenceHolder_Cleanup(&refs); - return -1; - } - // 5 == BatteryManager.BATTERY_STATUS_FULL - *charged = (status == 5) ? 1 : 0; - } - - if (battery) { - GET_BOOL_EXTRA(present, "present") // == BatteryManager.EXTRA_PRESENT (API 5) - *battery = present ? 1 : 0; - } - - if (seconds) { - *seconds = -1; // not possible - } - - if (percent) { - int level; - int scale; - - // Watch out for C89 scoping rules because of the macro - { - GET_INT_EXTRA(level_temp, "level") // == BatteryManager.EXTRA_LEVEL (API 5) - level = level_temp; - } - // Watch out for C89 scoping rules because of the macro - { - GET_INT_EXTRA(scale_temp, "scale") // == BatteryManager.EXTRA_SCALE (API 5) - scale = scale_temp; - } - - if ((level == -1) || (scale == -1)) { - LocalReferenceHolder_Cleanup(&refs); - return -1; - } - *percent = level * 100 / scale; - } - - (*env)->DeleteLocalRef(env, intent); - - LocalReferenceHolder_Cleanup(&refs); - 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) -{ - JNIEnv *env = Android_JNI_GetEnv(); - (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midDetectDevices); -} - -void Android_JNI_JoystickSetLED(int device_id, int red, int green, int blue) -{ - JNIEnv *env = Android_JNI_GetEnv(); - (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midJoystickSetLED, device_id, red, green, blue); -} - -void Android_JNI_JoystickSetSensorsEnabled(int device_id, bool enabled) -{ - JNIEnv *env = Android_JNI_GetEnv(); - (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midJoystickSetSensorsEnabled, device_id, (enabled == 1)); -} - -void Android_JNI_DetectHapticDevices(void) -{ - JNIEnv *env = Android_JNI_GetEnv(); - (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midDetectHapticDevices); -} - -void Android_JNI_HapticRun(int device_id, float intensity, int length) -{ - JNIEnv *env = Android_JNI_GetEnv(); - (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midHapticRun, device_id, intensity, length); -} - -void Android_JNI_HapticRumble(int device_id, float low_frequency_intensity, float high_frequency_intensity, int length) -{ - JNIEnv *env = Android_JNI_GetEnv(); - (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midHapticRumble, device_id, low_frequency_intensity, high_frequency_intensity, length); -} - -void Android_JNI_HapticStop(int device_id) -{ - JNIEnv *env = Android_JNI_GetEnv(); - (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midHapticStop, device_id); -} - // See SDLActivity.java for constants. #define COMMAND_SET_KEEP_SCREEN_ON 5 @@ -2782,130 +2305,6 @@ bool SDL_SendAndroidMessage(Uint32 command, int param) return Android_JNI_SendMessage(command, param); } -// sends message to be handled on the UI event dispatch thread -bool Android_JNI_SendMessage(int command, int param) -{ - JNIEnv *env = Android_JNI_GetEnv(); - return (*env)->CallStaticBooleanMethod(env, mActivityClass, midSendMessage, command, param); -} - -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(); - (*env)->CallStaticBooleanMethod(env, mActivityClass, midShowTextInput, - input_type, - inputRect->x, - inputRect->y, - inputRect->w, - inputRect->h); -} - -void Android_JNI_HideScreenKeyboard(void) -{ - // has to match Activity constant - 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) -{ - JNIEnv *env; - jclass clazz; - jmethodID mid; - jobject context; - jstring title; - jstring message; - jintArray button_flags; - jintArray button_ids; - jobjectArray button_texts; - jintArray colors; - jobject text; - jint temp; - int i; - - env = Android_JNI_GetEnv(); - - // convert parameters - - clazz = (*env)->FindClass(env, "java/lang/String"); - - title = (*env)->NewStringUTF(env, messageboxdata->title); - message = (*env)->NewStringUTF(env, messageboxdata->message); - - button_flags = (*env)->NewIntArray(env, messageboxdata->numbuttons); - button_ids = (*env)->NewIntArray(env, messageboxdata->numbuttons); - button_texts = (*env)->NewObjectArray(env, messageboxdata->numbuttons, - clazz, NULL); - for (i = 0; i < messageboxdata->numbuttons; ++i) { - const SDL_MessageBoxButtonData *sdlButton; - - if (messageboxdata->flags & SDL_MESSAGEBOX_BUTTONS_RIGHT_TO_LEFT) { - sdlButton = &messageboxdata->buttons[messageboxdata->numbuttons - 1 - i]; - } else { - sdlButton = &messageboxdata->buttons[i]; - } - - temp = sdlButton->flags; - (*env)->SetIntArrayRegion(env, button_flags, i, 1, &temp); - temp = sdlButton->buttonID; - (*env)->SetIntArrayRegion(env, button_ids, i, 1, &temp); - text = (*env)->NewStringUTF(env, sdlButton->text); - (*env)->SetObjectArrayElement(env, button_texts, i, text); - (*env)->DeleteLocalRef(env, text); - } - - if (messageboxdata->colorScheme) { - colors = (*env)->NewIntArray(env, SDL_MESSAGEBOX_COLOR_COUNT); - for (i = 0; i < SDL_MESSAGEBOX_COLOR_COUNT; ++i) { - temp = (0xFFU << 24) | - (messageboxdata->colorScheme->colors[i].r << 16) | - (messageboxdata->colorScheme->colors[i].g << 8) | - (messageboxdata->colorScheme->colors[i].b << 0); - (*env)->SetIntArrayRegion(env, colors, i, 1, &temp); - } - } else { - colors = NULL; - } - - (*env)->DeleteLocalRef(env, clazz); - - // context = SDLActivity.getContext(); - context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext); - - clazz = (*env)->GetObjectClass(env, context); - - mid = (*env)->GetMethodID(env, clazz, - "messageboxShowMessageBox", "(ILjava/lang/String;Ljava/lang/String;[I[I[Ljava/lang/String;[I)I"); - *buttonID = (*env)->CallIntMethod(env, context, mid, - (jint)messageboxdata->flags, - title, - message, - button_flags, - button_ids, - button_texts, - colors); - - (*env)->DeleteLocalRef(env, context); - (*env)->DeleteLocalRef(env, clazz); - - // delete parameters - - (*env)->DeleteLocalRef(env, title); - (*env)->DeleteLocalRef(env, message); - (*env)->DeleteLocalRef(env, button_flags); - (*env)->DeleteLocalRef(env, button_ids); - (*env)->DeleteLocalRef(env, button_texts); - (*env)->DeleteLocalRef(env, colors); - - return true; -} /* ////////////////////////////////////////////////////////////////////////////// @@ -2932,67 +2331,6 @@ void *SDL_GetAndroidActivity(void) return (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext); } -int SDL_GetAndroidSDKVersion(void) -{ - static int sdk_version; - if (!sdk_version) { - char sdk[PROP_VALUE_MAX] = { 0 }; - if (__system_property_get("ro.build.version.sdk", sdk) != 0) { - sdk_version = SDL_atoi(sdk); - } - } - return sdk_version; -} - -char *SDL_GetAndroidPackageName(void) -{ - // this doesn't currently cache this, because it's only used by SDL_GetExeName, which _does_ cache it. - struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(SDL_FUNCTION); - - JNIEnv *env = Android_JNI_GetEnv(); - if (!LocalReferenceHolder_Init(&refs, env)) { - LocalReferenceHolder_Cleanup(&refs); - return NULL; - } - - // context = SDLActivity.getContext(); - jobject context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext); - if (!context) { - SDL_SetError("Couldn't get Android context!"); - LocalReferenceHolder_Cleanup(&refs); - return NULL; - } - - // fileObj = context.getFilesDir(); - jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context), "getPackageName", "()Ljava/lang/String;"); - jstring jstr = (jstring)(*env)->CallObjectMethod(env, context, mid); - if (Android_JNI_ExceptionOccurred(false)) { - LocalReferenceHolder_Cleanup(&refs); - return NULL; - } - - const char *cstr = (*env)->GetStringUTFChars(env, jstr, NULL); - char *retval = cstr ? SDL_strdup(cstr) : NULL; - (*env)->ReleaseStringUTFChars(env, jstr, cstr); - - LocalReferenceHolder_Cleanup(&refs); - - return retval; -} - - -bool SDL_IsAndroidTablet(void) -{ - JNIEnv *env = Android_JNI_GetEnv(); - return (*env)->CallStaticBooleanMethod(env, mActivityClass, midIsTablet); -} - -bool SDL_IsAndroidTV(void) -{ - JNIEnv *env = Android_JNI_GetEnv(); - return (*env)->CallStaticBooleanMethod(env, mActivityClass, midIsAndroidTV); -} - bool SDL_IsChromebook(void) { JNIEnv *env = Android_JNI_GetEnv(); @@ -3005,14 +2343,6 @@ bool SDL_IsDeXMode(void) return (*env)->CallStaticBooleanMethod(env, mActivityClass, midIsDeXMode); } -void SDL_SendAndroidBackButton(void) -{ -#ifndef SDL_VIDEO_DISABLED - JNIEnv *env = Android_JNI_GetEnv(); - (*env)->CallStaticVoidMethod(env, mActivityClass, midManualBackButton); -#endif -} - const char *SDL_GetAndroidInternalStoragePath(void) { static char *s_AndroidInternalFilesPath = NULL; @@ -3203,69 +2533,6 @@ bool SDL_ShowAndroidToast(const char *message, int duration, int gravity, int xO return Android_JNI_ShowToast(message, duration, gravity, xOffset, yOffset); } -void Android_JNI_GetManifestEnvironmentVariables(void) -{ - if (!mActivityClass || !midGetManifestEnvironmentVariables) { - __android_log_print(ANDROID_LOG_WARN, "SDL", "Request to get environment variables before JNI is ready"); - return; - } - - if (!bHasEnvironmentVariables) { - JNIEnv *env = Android_JNI_GetEnv(); - bool ret = (*env)->CallStaticBooleanMethod(env, mActivityClass, midGetManifestEnvironmentVariables); - if (ret) { - bHasEnvironmentVariables = true; - } - } -} - -#ifndef SDL_VIDEO_DISABLED -int Android_JNI_CreateCustomCursor(SDL_Surface *surface, int hot_x, int hot_y) -{ - JNIEnv *env = Android_JNI_GetEnv(); - int custom_cursor = 0; - jintArray pixels; - pixels = (*env)->NewIntArray(env, surface->w * surface->h); - if (pixels) { - (*env)->SetIntArrayRegion(env, pixels, 0, surface->w * surface->h, (int *)surface->pixels); - custom_cursor = (*env)->CallStaticIntMethod(env, mActivityClass, midCreateCustomCursor, pixels, surface->w, surface->h, hot_x, hot_y); - (*env)->DeleteLocalRef(env, pixels); - } else { - SDL_OutOfMemory(); - } - return custom_cursor; -} - -void Android_JNI_DestroyCustomCursor(int cursorID) -{ - JNIEnv *env = Android_JNI_GetEnv(); - (*env)->CallStaticVoidMethod(env, mActivityClass, midDestroyCustomCursor, cursorID); -} - -bool Android_JNI_SetCustomCursor(int cursorID) -{ - JNIEnv *env = Android_JNI_GetEnv(); - return (*env)->CallStaticBooleanMethod(env, mActivityClass, midSetCustomCursor, cursorID); -} - -bool Android_JNI_SetSystemCursor(int cursorID) -{ - JNIEnv *env = Android_JNI_GetEnv(); - return (*env)->CallStaticBooleanMethod(env, mActivityClass, midSetSystemCursor, cursorID); -} - -bool Android_JNI_SupportsRelativeMouse(void) -{ - JNIEnv *env = Android_JNI_GetEnv(); - return (*env)->CallStaticBooleanMethod(env, mActivityClass, midSupportsRelativeMouse); -} - -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 { @@ -3337,101 +2604,7 @@ bool SDL_RequestAndroidPermission(const char *permission, SDL_RequestAndroidPerm return true; } -// Show toast notification -bool Android_JNI_ShowToast(const char *message, int duration, int gravity, int xOffset, int yOffset) -{ - bool result; - JNIEnv *env = Android_JNI_GetEnv(); - jstring jmessage = (*env)->NewStringUTF(env, message); - result = (*env)->CallStaticBooleanMethod(env, mActivityClass, midShowToast, jmessage, duration, gravity, xOffset, yOffset); - (*env)->DeleteLocalRef(env, jmessage); - return result; -} - -bool Android_JNI_GetLocale(char *buf, size_t buflen) -{ - bool result = false; - if (buf && buflen > 0) { - *buf = '\0'; - JNIEnv *env = Android_JNI_GetEnv(); - jstring string = (jstring)(*env)->CallStaticObjectMethod(env, mActivityClass, midGetPreferredLocales); - if (string) { - const char *utf8string = (*env)->GetStringUTFChars(env, string, NULL); - if (utf8string) { - result = true; - SDL_strlcpy(buf, utf8string, buflen); - (*env)->ReleaseStringUTFChars(env, string, utf8string); - } - (*env)->DeleteLocalRef(env, string); - } - } - return result; -} - -bool Android_JNI_OpenURL(const char *url) -{ - bool result; - JNIEnv *env = Android_JNI_GetEnv(); - jstring jurl = (*env)->NewStringUTF(env, url); - result = (*env)->CallStaticBooleanMethod(env, mActivityClass, midOpenURL, jurl); - (*env)->DeleteLocalRef(env, jurl); - return result; -} - -int Android_JNI_OpenFileDescriptor(const char *uri, const char *mode) -{ - // Get fopen-style modes - int moderead = 0, modewrite = 0, modeappend = 0, modeupdate = 0; - - for (const char *cmode = mode; *cmode; cmode++) { - switch (*cmode) { - case 'a': - modeappend = 1; - break; - case 'r': - moderead = 1; - break; - case 'w': - modewrite = 1; - break; - case '+': - modeupdate = 1; - break; - default: - break; - } - } - - // Translate fopen-style modes to ContentResolver modes. - // Android only allows "r", "w", "wt", "wa", "rw" or "rwt". - const char *contentResolverMode = "r"; - - if (moderead) { - if (modewrite) { - contentResolverMode = "rwt"; - } else { - contentResolverMode = modeupdate ? "rw" : "r"; - } - } else if (modewrite) { - contentResolverMode = modeupdate ? "rwt" : "wt"; - } else if (modeappend) { - contentResolverMode = modeupdate ? "rw" : "wa"; - } - - JNIEnv *env = Android_JNI_GetEnv(); - jstring jstringUri = (*env)->NewStringUTF(env, uri); - jstring jstringMode = (*env)->NewStringUTF(env, contentResolverMode); - jint fd = (*env)->CallStaticIntMethod(env, mActivityClass, midOpenFileDescriptor, jstringUri, jstringMode); - (*env)->DeleteLocalRef(env, jstringUri); - (*env)->DeleteLocalRef(env, jstringMode); - - if (fd == -1) { - SDL_SetError("Unspecified error in JNI"); - } - - return fd; -} - +#ifndef SDL_DIALOG_DISABLED static struct AndroidFileDialog { int request_code; @@ -3507,7 +2680,963 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeFileDialog)( SDL_free(charFileList); } } +#endif // !SDL_DIALOG_DISABLED +/******************************************************************************* + Interface functions (declared in SDL_android.h) + Ordered to match SDL_android.h. +*******************************************************************************/ + +#ifndef SDL_AUDIO_DISABLED +// Audio support +void Android_StartAudioHotplug(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording) +{ + JNIEnv *env = Android_JNI_GetEnv(); + // this will fire the callback for each existing device right away (which will eventually SDL_AddAudioDevice), and again later when things change. + (*env)->CallStaticVoidMethod(env, mAudioManagerClass, midRegisterAudioDeviceCallback); + *default_playback = *default_recording = NULL; // !!! FIXME: how do you decide the default device id? +} + +void Android_StopAudioHotplug(void) +{ + JNIEnv *env = Android_JNI_GetEnv(); + (*env)->CallStaticVoidMethod(env, mAudioManagerClass, midUnregisterAudioDeviceCallback); +} + +void Android_AudioThreadInit(SDL_AudioDevice *device) +{ + Android_JNI_AudioSetThreadPriority((int) device->recording, (int)device->instance_id); +} +#endif // !SDL_AUDIO_DISABLED + +// Life cycle +void Android_SendLifecycleEvent(SDL_AndroidLifecycleEvent event) +{ + SDL_LockMutex(Android_LifecycleMutex); + { + int index; + bool add_event = true; + + switch (event) { + case SDL_ANDROID_LIFECYCLE_WAKE: + // We don't need more than one wake queued + index = FindLifecycleEvent(SDL_ANDROID_LIFECYCLE_WAKE); + if (index >= 0) { + add_event = false; + } + break; + case SDL_ANDROID_LIFECYCLE_PAUSE: + // If we have a resume queued, just stay in the paused state + index = FindLifecycleEvent(SDL_ANDROID_LIFECYCLE_RESUME); + if (index >= 0) { + RemoveLifecycleEvent(index); + add_event = false; + } + break; + case SDL_ANDROID_LIFECYCLE_RESUME: + // If we have a pause queued, just stay in the resumed state + index = FindLifecycleEvent(SDL_ANDROID_LIFECYCLE_PAUSE); + if (index >= 0) { + RemoveLifecycleEvent(index); + add_event = false; + } + break; + case SDL_ANDROID_LIFECYCLE_LOWMEMORY: + // We don't need more than one low memory event queued + index = FindLifecycleEvent(SDL_ANDROID_LIFECYCLE_LOWMEMORY); + if (index >= 0) { + add_event = false; + } + break; + case SDL_ANDROID_LIFECYCLE_DESTROY: + // Remove all other events, we're done! + while (Android_NumLifecycleEvents > 0) { + RemoveLifecycleEvent(0); + } + break; + default: + SDL_assert(!"Sending unexpected lifecycle event"); + add_event = false; + break; + } + + if (add_event) { + SDL_assert(Android_NumLifecycleEvents < SDL_arraysize(Android_LifecycleEvents)); + Android_LifecycleEvents[Android_NumLifecycleEvents++] = event; + SDL_SignalSemaphore(Android_LifecycleEventSem); + } + } + SDL_UnlockMutex(Android_LifecycleMutex); +} + +bool Android_WaitLifecycleEvent(SDL_AndroidLifecycleEvent *event, Sint64 timeoutNS) +{ + bool got_event = false; + + int relock_count = 0; + Android_LockActivityMutex(); + while (Android_ActivityMutexCount > 1) { + // We came into this function with the activity lock held, we need to unlock so lifecycle events can be dispatched + ++relock_count; + Android_UnlockActivityMutex(); + } + Android_UnlockActivityMutex(); + + while (!got_event && SDL_WaitSemaphoreTimeoutNS(Android_LifecycleEventSem, timeoutNS)) { + SDL_LockMutex(Android_LifecycleMutex); + { + if (Android_NumLifecycleEvents > 0) { + *event = Android_LifecycleEvents[0]; + RemoveLifecycleEvent(0); + got_event = true; + } + } + SDL_UnlockMutex(Android_LifecycleMutex); + } + + while (relock_count > 0) { + Android_LockActivityMutex(); + --relock_count; + } + return got_event; +} + +void Android_LockActivityMutex(void) +{ + SDL_LockMutex(Android_ActivityMutex); + ++Android_ActivityMutexCount; +} + +void Android_UnlockActivityMutex(void) +{ + --Android_ActivityMutexCount; + SDL_UnlockMutex(Android_ActivityMutex); +} + +void Android_SetAllowRecreateActivity(bool enabled) +{ + allow_recreate_activity = enabled; + allow_recreate_activity_set = true; +} + +#ifndef SDL_VIDEO_DISABLED +// Interface from the SDL library into the Android Java activity +void Android_JNI_SetActivityTitle(const char *title) +{ + JNIEnv *env = Android_JNI_GetEnv(); + + jstring jtitle = (*env)->NewStringUTF(env, title); + (*env)->CallStaticBooleanMethod(env, mActivityClass, midSetActivityTitle, jtitle); + (*env)->DeleteLocalRef(env, jtitle); +} + +void Android_JNI_SetWindowStyle(bool fullscreen) +{ + JNIEnv *env = Android_JNI_GetEnv(); + (*env)->CallStaticVoidMethod(env, mActivityClass, midSetWindowStyle, fullscreen ? 1 : 0); +} + +void Android_JNI_SetOrientation(int w, int h, int resizable, const char *hint) +{ + JNIEnv *env = Android_JNI_GetEnv(); + + jstring jhint = (*env)->NewStringUTF(env, (hint ? hint : "")); + (*env)->CallStaticVoidMethod(env, mActivityClass, midSetOrientation, w, h, (resizable ? 1 : 0), jhint); + (*env)->DeleteLocalRef(env, jhint); +} + +void Android_JNI_MinimizeWindow(void) +{ + JNIEnv *env = Android_JNI_GetEnv(); + (*env)->CallStaticVoidMethod(env, mActivityClass, midMinimizeWindow); +} + +bool Android_JNI_ShouldMinimizeOnFocusLoss(void) +{ + JNIEnv *env = Android_JNI_GetEnv(); + return (*env)->CallStaticBooleanMethod(env, mActivityClass, midShouldMinimizeOnFocusLoss); +} + +void Android_JNI_ShowScreenKeyboard(int input_type, SDL_Rect *inputRect) +{ + JNIEnv *env = Android_JNI_GetEnv(); + (*env)->CallStaticBooleanMethod(env, mActivityClass, midShowTextInput, + input_type, + inputRect->x, + inputRect->y, + inputRect->w, + inputRect->h); +} + +void Android_JNI_HideScreenKeyboard(void) +{ + // has to match Activity constant + const int COMMAND_TEXTEDIT_HIDE = 3; + Android_JNI_SendMessage(COMMAND_TEXTEDIT_HIDE, 0); +} + +bool Android_JNI_SuspendScreenSaver(bool suspend) +{ + return Android_JNI_SendMessage(COMMAND_SET_KEEP_SCREEN_ON, (suspend == false) ? 0 : 1); +} + +ANativeWindow *Android_JNI_GetNativeWindow(void) +{ + ANativeWindow *anw = NULL; + jobject s; + JNIEnv *env = Android_JNI_GetEnv(); + + s = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetNativeSurface); + if (s) { + anw = ANativeWindow_fromSurface(env, s); + (*env)->DeleteLocalRef(env, s); + } + + return anw; +} + +SDL_DisplayOrientation Android_JNI_GetDisplayNaturalOrientation(void) +{ + return displayNaturalOrientation; +} + +SDL_DisplayOrientation Android_JNI_GetDisplayCurrentOrientation(void) +{ + return displayCurrentOrientation; +} + +// Clipboard support +bool Android_JNI_SetClipboardText(const char *text) +{ + JNIEnv *env = Android_JNI_GetEnv(); + jstring string = (*env)->NewStringUTF(env, text); + (*env)->CallStaticVoidMethod(env, mActivityClass, midClipboardSetText, string); + (*env)->DeleteLocalRef(env, string); + return true; +} + +char *Android_JNI_GetClipboardText(void) +{ + JNIEnv *env = Android_JNI_GetEnv(); + char *text = NULL; + jstring string; + + string = (*env)->CallStaticObjectMethod(env, mActivityClass, midClipboardGetText); + if (string) { + const char *utf = (*env)->GetStringUTFChars(env, string, 0); + if (utf) { + text = SDL_strdup(utf); + (*env)->ReleaseStringUTFChars(env, string, utf); + } + (*env)->DeleteLocalRef(env, string); + } + + return (!text) ? SDL_strdup("") : text; +} + +bool Android_JNI_HasClipboardText(void) +{ + JNIEnv *env = Android_JNI_GetEnv(); + return (*env)->CallStaticBooleanMethod(env, mActivityClass, midClipboardHasText); +} + +// Touch support +void Android_JNI_InitTouch(void) +{ + JNIEnv *env = Android_JNI_GetEnv(); + (*env)->CallStaticVoidMethod(env, mActivityClass, midInitTouch); +} + +// Cursor support +int Android_JNI_CreateCustomCursor(SDL_Surface *surface, int hot_x, int hot_y) +{ + JNIEnv *env = Android_JNI_GetEnv(); + int custom_cursor = 0; + jintArray pixels; + pixels = (*env)->NewIntArray(env, surface->w * surface->h); + if (pixels) { + (*env)->SetIntArrayRegion(env, pixels, 0, surface->w * surface->h, (int *)surface->pixels); + custom_cursor = (*env)->CallStaticIntMethod(env, mActivityClass, midCreateCustomCursor, pixels, surface->w, surface->h, hot_x, hot_y); + (*env)->DeleteLocalRef(env, pixels); + } else { + SDL_OutOfMemory(); + } + return custom_cursor; +} + +void Android_JNI_DestroyCustomCursor(int cursorID) +{ + JNIEnv *env = Android_JNI_GetEnv(); + (*env)->CallStaticVoidMethod(env, mActivityClass, midDestroyCustomCursor, cursorID); +} + +bool Android_JNI_SetCustomCursor(int cursorID) +{ + JNIEnv *env = Android_JNI_GetEnv(); + return (*env)->CallStaticBooleanMethod(env, mActivityClass, midSetCustomCursor, cursorID); +} + +bool Android_JNI_SetSystemCursor(int cursorID) +{ + JNIEnv *env = Android_JNI_GetEnv(); + return (*env)->CallStaticBooleanMethod(env, mActivityClass, midSetSystemCursor, cursorID); +} + +// Relative mouse support +bool Android_JNI_SupportsRelativeMouse(void) +{ + JNIEnv *env = Android_JNI_GetEnv(); + return (*env)->CallStaticBooleanMethod(env, mActivityClass, midSupportsRelativeMouse); +} + +bool Android_JNI_SetRelativeMouseEnabled(bool enabled) +{ + JNIEnv *env = Android_JNI_GetEnv(); + return (*env)->CallStaticBooleanMethod(env, mActivityClass, midSetRelativeMouseEnabled, (enabled == 1)); +} + +// MessageBox +bool Android_JNI_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID) +{ + JNIEnv *env; + jclass clazz; + jmethodID mid; + jobject context; + jstring title; + jstring message; + jintArray button_flags; + jintArray button_ids; + jobjectArray button_texts; + jintArray colors; + jobject text; + jint temp; + int i; + + env = Android_JNI_GetEnv(); + + // convert parameters + + clazz = (*env)->FindClass(env, "java/lang/String"); + + title = (*env)->NewStringUTF(env, messageboxdata->title); + message = (*env)->NewStringUTF(env, messageboxdata->message); + + button_flags = (*env)->NewIntArray(env, messageboxdata->numbuttons); + button_ids = (*env)->NewIntArray(env, messageboxdata->numbuttons); + button_texts = (*env)->NewObjectArray(env, messageboxdata->numbuttons, + clazz, NULL); + for (i = 0; i < messageboxdata->numbuttons; ++i) { + const SDL_MessageBoxButtonData *sdlButton; + + if (messageboxdata->flags & SDL_MESSAGEBOX_BUTTONS_RIGHT_TO_LEFT) { + sdlButton = &messageboxdata->buttons[messageboxdata->numbuttons - 1 - i]; + } else { + sdlButton = &messageboxdata->buttons[i]; + } + + temp = sdlButton->flags; + (*env)->SetIntArrayRegion(env, button_flags, i, 1, &temp); + temp = sdlButton->buttonID; + (*env)->SetIntArrayRegion(env, button_ids, i, 1, &temp); + text = (*env)->NewStringUTF(env, sdlButton->text); + (*env)->SetObjectArrayElement(env, button_texts, i, text); + (*env)->DeleteLocalRef(env, text); + } + + if (messageboxdata->colorScheme) { + colors = (*env)->NewIntArray(env, SDL_MESSAGEBOX_COLOR_COUNT); + for (i = 0; i < SDL_MESSAGEBOX_COLOR_COUNT; ++i) { + temp = (0xFFU << 24) | + (messageboxdata->colorScheme->colors[i].r << 16) | + (messageboxdata->colorScheme->colors[i].g << 8) | + (messageboxdata->colorScheme->colors[i].b << 0); + (*env)->SetIntArrayRegion(env, colors, i, 1, &temp); + } + } else { + colors = NULL; + } + + (*env)->DeleteLocalRef(env, clazz); + + // context = SDLActivity.getContext(); + context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext); + + clazz = (*env)->GetObjectClass(env, context); + + mid = (*env)->GetMethodID(env, clazz, + "messageboxShowMessageBox", "(ILjava/lang/String;Ljava/lang/String;[I[I[Ljava/lang/String;[I)I"); + *buttonID = (*env)->CallIntMethod(env, context, mid, + (jint)messageboxdata->flags, + title, + message, + button_flags, + button_ids, + button_texts, + colors); + + (*env)->DeleteLocalRef(env, context); + (*env)->DeleteLocalRef(env, clazz); + + // delete parameters + + (*env)->DeleteLocalRef(env, title); + (*env)->DeleteLocalRef(env, message); + (*env)->DeleteLocalRef(env, button_flags); + (*env)->DeleteLocalRef(env, button_ids); + (*env)->DeleteLocalRef(env, button_texts); + (*env)->DeleteLocalRef(env, colors); + + return true; +} +#endif // !SDL_VIDEO_DISABLED + +bool Android_JNI_FileOpen(void **puserdata, const char *fileName, const char *mode) +{ + SDL_assert(puserdata != NULL); + + AAsset *asset = NULL; + *puserdata = NULL; + + if (!asset_manager) { + Internal_Android_Create_AssetManager(); + if (!asset_manager) { + return SDL_SetError("Couldn't create asset manager"); + } + } + + fileName = GetAssetPath(fileName); + + asset = AAssetManager_open(asset_manager, fileName, AASSET_MODE_UNKNOWN); + if (!asset) { + return SDL_SetError("Couldn't open asset '%s'", fileName); + } + + *puserdata = (void *)asset; + return true; +} + +Sint64 Android_JNI_FileSize(void *userdata) +{ + return (Sint64) AAsset_getLength64((AAsset *)userdata); +} + +Sint64 Android_JNI_FileSeek(void *userdata, Sint64 offset, SDL_IOWhence whence) +{ + return (Sint64) AAsset_seek64((AAsset *)userdata, offset, (int)whence); +} + +size_t Android_JNI_FileRead(void *userdata, void *buffer, size_t size, SDL_IOStatus *status) +{ + const int bytes = AAsset_read((AAsset *)userdata, buffer, size); + if (bytes < 0) { + SDL_SetError("AAsset_read() failed"); + *status = SDL_IO_STATUS_ERROR; + return 0; + } else if (bytes < size) { + *status = SDL_IO_STATUS_EOF; + } + return (size_t)bytes; +} + +size_t Android_JNI_FileWrite(void *userdata, const void *buffer, size_t size, SDL_IOStatus *status) +{ + SDL_SetError("Cannot write to Android package filesystem"); + *status = SDL_IO_STATUS_ERROR; + return 0; +} + +bool Android_JNI_FileClose(void *userdata) +{ + AAsset_close((AAsset *)userdata); + return true; +} + +bool Android_JNI_EnumerateAssetDirectory(const char *path, SDL_EnumerateDirectoryCallback cb, void *userdata) +{ + SDL_assert(path != NULL); + + if (!PrepareAPK()) { + return false; + } + + SDL_EnumerationResult result = SDL_ENUM_CONTINUE; + const char *asset_path = GetAssetPath(path); + + const APKNode *apknode = FindAPKNode(asset_path); + if (!apknode) { + return SDL_SetError("No such directory"); + } else if (apknode->info.type != SDL_PATHTYPE_DIRECTORY) { + return SDL_SetError("Not a directory"); + } else { + for (const APKNode *node = apknode->children; node && (result == SDL_ENUM_CONTINUE); node = node->next_sibling) { + result = cb(userdata, path, node->name); + } + } + + return (result != SDL_ENUM_FAILURE); +} + +bool Android_JNI_GetAssetPathInfo(const char *path, SDL_PathInfo *info) +{ + SDL_assert(path != NULL); + + if (!PrepareAPK()) { + return false; + } + + path = GetAssetPath(path); + const APKNode *apknode = FindAPKNode(path); + if (!apknode) { + return SDL_SetError("No such file or directory"); + } + SDL_copyp(info, &apknode->info); + return true; +} + +// Environment support +void Android_JNI_GetManifestEnvironmentVariables(void) +{ + if (!mActivityClass || !midGetManifestEnvironmentVariables) { + __android_log_print(ANDROID_LOG_WARN, "SDL", "Request to get environment variables before JNI is ready"); + return; + } + + if (!bHasEnvironmentVariables) { + JNIEnv *env = Android_JNI_GetEnv(); + bool ret = (*env)->CallStaticBooleanMethod(env, mActivityClass, midGetManifestEnvironmentVariables); + if (ret) { + bHasEnvironmentVariables = true; + } + } +} + +int Android_JNI_OpenFileDescriptor(const char *uri, const char *mode) +{ + // Get fopen-style modes + int moderead = 0, modewrite = 0, modeappend = 0, modeupdate = 0; + + for (const char *cmode = mode; *cmode; cmode++) { + switch (*cmode) { + case 'a': + modeappend = 1; + break; + case 'r': + moderead = 1; + break; + case 'w': + modewrite = 1; + break; + case '+': + modeupdate = 1; + break; + default: + break; + } + } + + // Translate fopen-style modes to ContentResolver modes. + // Android only allows "r", "w", "wt", "wa", "rw" or "rwt". + const char *contentResolverMode = "r"; + + if (moderead) { + if (modewrite) { + contentResolverMode = "rwt"; + } else { + contentResolverMode = modeupdate ? "rw" : "r"; + } + } else if (modewrite) { + contentResolverMode = modeupdate ? "rwt" : "wt"; + } else if (modeappend) { + contentResolverMode = modeupdate ? "rw" : "wa"; + } + + JNIEnv *env = Android_JNI_GetEnv(); + jstring jstringUri = (*env)->NewStringUTF(env, uri); + jstring jstringMode = (*env)->NewStringUTF(env, contentResolverMode); + jint fd = (*env)->CallStaticIntMethod(env, mActivityClass, midOpenFileDescriptor, jstringUri, jstringMode); + (*env)->DeleteLocalRef(env, jstringUri); + (*env)->DeleteLocalRef(env, jstringMode); + + if (fd == -1) { + SDL_SetError("Unspecified error in JNI"); + } + + return fd; +} + +#ifndef SDL_POWER_DISABLED +/* returns 0 on success or -1 on error (others undefined then) + * returns truthy or falsy value in plugged, charged and battery + * returns the value in seconds and percent or -1 if not available + */ +int Android_JNI_GetPowerInfo(int *plugged, int *charged, int *battery, int *seconds, int *percent) +{ + struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(SDL_FUNCTION); + JNIEnv *env = Android_JNI_GetEnv(); + jmethodID mid; + jobject context; + jstring action; + jclass cls; + jobject filter; + jobject intent; + jstring iname; + jmethodID imid; + jstring bname; + jmethodID bmid; + if (!LocalReferenceHolder_Init(&refs, env)) { + LocalReferenceHolder_Cleanup(&refs); + return -1; + } + + // context = SDLActivity.getContext(); + context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext); + + action = (*env)->NewStringUTF(env, "android.intent.action.BATTERY_CHANGED"); + + cls = (*env)->FindClass(env, "android/content/IntentFilter"); + + mid = (*env)->GetMethodID(env, cls, "", "(Ljava/lang/String;)V"); + filter = (*env)->NewObject(env, cls, mid, action); + + (*env)->DeleteLocalRef(env, action); + + mid = (*env)->GetMethodID(env, mActivityClass, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;"); + intent = (*env)->CallObjectMethod(env, context, mid, NULL, filter); + + (*env)->DeleteLocalRef(env, filter); + + cls = (*env)->GetObjectClass(env, intent); + + imid = (*env)->GetMethodID(env, cls, "getIntExtra", "(Ljava/lang/String;I)I"); + + // Watch out for C89 scoping rules because of the macro +#define GET_INT_EXTRA(var, key) \ + int var; \ + iname = (*env)->NewStringUTF(env, key); \ + (var) = (*env)->CallIntMethod(env, intent, imid, iname, -1); \ + (*env)->DeleteLocalRef(env, iname); + + bmid = (*env)->GetMethodID(env, cls, "getBooleanExtra", "(Ljava/lang/String;Z)Z"); + + // Watch out for C89 scoping rules because of the macro +#define GET_BOOL_EXTRA(var, key) \ + int var; \ + bname = (*env)->NewStringUTF(env, key); \ + (var) = (*env)->CallBooleanMethod(env, intent, bmid, bname, JNI_FALSE); \ + (*env)->DeleteLocalRef(env, bname); + + if (plugged) { + // Watch out for C89 scoping rules because of the macro + GET_INT_EXTRA(plug, "plugged") // == BatteryManager.EXTRA_PLUGGED (API 5) + if (plug == -1) { + LocalReferenceHolder_Cleanup(&refs); + return -1; + } + // 1 == BatteryManager.BATTERY_PLUGGED_AC + // 2 == BatteryManager.BATTERY_PLUGGED_USB + *plugged = (0 < plug) ? 1 : 0; + } + + if (charged) { + // Watch out for C89 scoping rules because of the macro + GET_INT_EXTRA(status, "status") // == BatteryManager.EXTRA_STATUS (API 5) + if (status == -1) { + LocalReferenceHolder_Cleanup(&refs); + return -1; + } + // 5 == BatteryManager.BATTERY_STATUS_FULL + *charged = (status == 5) ? 1 : 0; + } + + if (battery) { + GET_BOOL_EXTRA(present, "present") // == BatteryManager.EXTRA_PRESENT (API 5) + *battery = present ? 1 : 0; + } + + if (seconds) { + *seconds = -1; // not possible + } + + if (percent) { + int level; + int scale; + + // Watch out for C89 scoping rules because of the macro + { + GET_INT_EXTRA(level_temp, "level") // == BatteryManager.EXTRA_LEVEL (API 5) + level = level_temp; + } + // Watch out for C89 scoping rules because of the macro + { + GET_INT_EXTRA(scale_temp, "scale") // == BatteryManager.EXTRA_SCALE (API 5) + scale = scale_temp; + } + + if ((level == -1) || (scale == -1)) { + LocalReferenceHolder_Cleanup(&refs); + return -1; + } + *percent = level * 100 / scale; + } + + (*env)->DeleteLocalRef(env, intent); + + LocalReferenceHolder_Cleanup(&refs); + return 0; +} +#endif // !SDL_POWER_DISABLED + +#ifndef SDL_JOYSTICK_DISABLED +void Android_JNI_DetectDevices(void) +{ + JNIEnv *env = Android_JNI_GetEnv(); + (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midDetectDevices); +} + +void Android_JNI_JoystickSetLED(int device_id, int red, int green, int blue) +{ + JNIEnv *env = Android_JNI_GetEnv(); + (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midJoystickSetLED, device_id, red, green, blue); +} + +void Android_JNI_JoystickSetSensorsEnabled(int device_id, bool enabled) +{ + JNIEnv *env = Android_JNI_GetEnv(); + (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midJoystickSetSensorsEnabled, device_id, (enabled == 1)); +} +#endif // !SDL_JOYSTICK_DISABLED + +#ifndef SDL_HAPTIC_DISABLED +void Android_JNI_DetectHapticDevices(void) +{ + JNIEnv *env = Android_JNI_GetEnv(); + (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midDetectHapticDevices); +} + +void Android_JNI_HapticRun(int device_id, float intensity, int length) +{ + JNIEnv *env = Android_JNI_GetEnv(); + (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midHapticRun, device_id, intensity, length); +} + +void Android_JNI_HapticRumble(int device_id, float low_frequency_intensity, float high_frequency_intensity, int length) +{ + JNIEnv *env = Android_JNI_GetEnv(); + (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midHapticRumble, device_id, low_frequency_intensity, high_frequency_intensity, length); +} + +void Android_JNI_HapticStop(int device_id) +{ + JNIEnv *env = Android_JNI_GetEnv(); + (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midHapticStop, device_id); +} +#endif // !SDL_HAPTIC_DISABLED + +// Threads +// Get local storage value +JNIEnv *Android_JNI_GetEnv(void) +{ + // Get JNIEnv from the Thread local storage + JNIEnv *env = pthread_getspecific(mThreadKey); + if (!env) { + // If it fails, try to attach ! (e.g the thread isn't created with SDL_CreateThread() + int status; + + // There should be a JVM + if (!mJavaVM) { + __android_log_print(ANDROID_LOG_ERROR, "SDL", "Failed, there is no JavaVM"); + return NULL; + } + + /* Attach the current thread to the JVM and get a JNIEnv. + * It will be detached by pthread_create destructor 'Android_JNI_ThreadDestroyed' */ + status = (*mJavaVM)->AttachCurrentThread(mJavaVM, &env, NULL); + if (status < 0) { + __android_log_print(ANDROID_LOG_ERROR, "SDL", "Failed to attach current thread (err=%d)", status); + return NULL; + } + + // Save JNIEnv into the Thread local storage + if (!Android_JNI_SetEnv(env)) { + return NULL; + } + } + + return env; +} + +// Set up an external thread for using JNI with Android_JNI_GetEnv() +bool Android_JNI_SetupThread(void) +{ + JNIEnv *env; + int status; + + // There should be a JVM + if (!mJavaVM) { + __android_log_print(ANDROID_LOG_ERROR, "SDL", "Failed, there is no JavaVM"); + return false; + } + + /* Attach the current thread to the JVM and get a JNIEnv. + * It will be detached by pthread_create destructor 'Android_JNI_ThreadDestroyed' */ + status = (*mJavaVM)->AttachCurrentThread(mJavaVM, &env, NULL); + if (status < 0) { + __android_log_print(ANDROID_LOG_ERROR, "SDL", "Failed to attach current thread (err=%d)", status); + return false; + } + + // Save JNIEnv into the Thread local storage + if (!Android_JNI_SetEnv(env)) { + return false; + } + + return true; +} + +// Locale +bool Android_JNI_GetLocale(char *buf, size_t buflen) +{ + bool result = false; + if (buf && buflen > 0) { + *buf = '\0'; + JNIEnv *env = Android_JNI_GetEnv(); + jstring string = (jstring)(*env)->CallStaticObjectMethod(env, mActivityClass, midGetPreferredLocales); + if (string) { + const char *utf8string = (*env)->GetStringUTFChars(env, string, NULL); + if (utf8string) { + result = true; + SDL_strlcpy(buf, utf8string, buflen); + (*env)->ReleaseStringUTFChars(env, string, utf8string); + } + (*env)->DeleteLocalRef(env, string); + } + } + return result; +} + +// Generic messages +// sends message to be handled on the UI event dispatch thread +bool Android_JNI_SendMessage(int command, int param) +{ + JNIEnv *env = Android_JNI_GetEnv(); + return (*env)->CallStaticBooleanMethod(env, mActivityClass, midSendMessage, command, param); +} + +// Show toast notification +bool Android_JNI_ShowToast(const char *message, int duration, int gravity, int xOffset, int yOffset) +{ + bool result; + JNIEnv *env = Android_JNI_GetEnv(); + jstring jmessage = (*env)->NewStringUTF(env, message); + result = (*env)->CallStaticBooleanMethod(env, mActivityClass, midShowToast, jmessage, duration, gravity, xOffset, yOffset); + (*env)->DeleteLocalRef(env, jmessage); + return result; +} + +bool Android_JNI_OpenURL(const char *url) +{ + bool result; + JNIEnv *env = Android_JNI_GetEnv(); + jstring jurl = (*env)->NewStringUTF(env, url); + result = (*env)->CallStaticBooleanMethod(env, mActivityClass, midOpenURL, jurl); + (*env)->DeleteLocalRef(env, jurl); + return result; +} + +int SDL_GetAndroidSDKVersion(void) +{ + static int sdk_version; + if (!sdk_version) { + char sdk[PROP_VALUE_MAX] = { 0 }; + if (__system_property_get("ro.build.version.sdk", sdk) != 0) { + sdk_version = SDL_atoi(sdk); + } + } + return sdk_version; +} + +bool SDL_IsAndroidTablet(void) +{ + JNIEnv *env = Android_JNI_GetEnv(); + return (*env)->CallStaticBooleanMethod(env, mActivityClass, midIsTablet); +} + +bool SDL_IsAndroidTV(void) +{ + JNIEnv *env = Android_JNI_GetEnv(); + return (*env)->CallStaticBooleanMethod(env, mActivityClass, midIsAndroidTV); +} + +SDL_FormFactor SDL_GetAndroidDeviceFormFactor(void) +{ + JNIEnv *env = Android_JNI_GetEnv(); + SDL_FormFactor form_factor = SDL_FORMFACTOR_UNKNOWN; + jstring string; + + string = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetDeviceFormFactor); + if (string) { + const char *utf = (*env)->GetStringUTFChars(env, string, 0); + if (utf) { + if (SDL_strcmp(utf, "tv") == 0) { + form_factor = SDL_FORMFACTOR_TV; + } else if (SDL_strcmp(utf, "tablet") == 0) { + form_factor = SDL_FORMFACTOR_TABLET; + } else if (SDL_strcmp(utf, "phone") == 0) { + form_factor = SDL_FORMFACTOR_PHONE; + } else if (SDL_strcmp(utf, "car") == 0) { + form_factor = SDL_FORMFACTOR_CAR; + } else if (SDL_strcmp(utf, "headset") == 0) { + form_factor = SDL_FORMFACTOR_HEADSET; + } else if (SDL_strcmp(utf, "watch") == 0) { + form_factor = SDL_FORMFACTOR_WATCH; + } else { + form_factor = SDL_FORMFACTOR_UNKNOWN; + } + (*env)->ReleaseStringUTFChars(env, string, utf); + } + (*env)->DeleteLocalRef(env, string); + } + + return form_factor; +} + +char *SDL_GetAndroidPackageName(void) +{ + // this doesn't currently cache this, because it's only used by SDL_GetExeName, which _does_ cache it. + struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(SDL_FUNCTION); + + JNIEnv *env = Android_JNI_GetEnv(); + if (!LocalReferenceHolder_Init(&refs, env)) { + LocalReferenceHolder_Cleanup(&refs); + return NULL; + } + + // context = SDLActivity.getContext(); + jobject context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext); + if (!context) { + SDL_SetError("Couldn't get Android context!"); + LocalReferenceHolder_Cleanup(&refs); + return NULL; + } + + // fileObj = context.getFilesDir(); + jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context), "getPackageName", "()Ljava/lang/String;"); + jstring jstr = (jstring)(*env)->CallObjectMethod(env, context, mid); + if (Android_JNI_ExceptionOccurred(false)) { + LocalReferenceHolder_Cleanup(&refs); + return NULL; + } + + const char *cstr = (*env)->GetStringUTFChars(env, jstr, NULL); + char *retval = cstr ? SDL_strdup(cstr) : NULL; + (*env)->ReleaseStringUTFChars(env, jstr, cstr); + + LocalReferenceHolder_Cleanup(&refs); + + return retval; +} + +#ifndef SDL_DIALOG_DISABLED bool Android_JNI_ShowFileDialog( SDL_DialogFileCallback callback, void *userdata, const SDL_DialogFileFilter *filters, int nfilters, SDL_FileDialogType type, @@ -3583,38 +3712,6 @@ bool Android_JNI_ShowFileDialog( return true; } - -SDL_FormFactor SDL_GetAndroidDeviceFormFactor(void) -{ - JNIEnv *env = Android_JNI_GetEnv(); - SDL_FormFactor form_factor = SDL_FORMFACTOR_UNKNOWN; - jstring string; - - string = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetDeviceFormFactor); - if (string) { - const char *utf = (*env)->GetStringUTFChars(env, string, 0); - if (utf) { - if (SDL_strcmp(utf, "tv") == 0) { - form_factor = SDL_FORMFACTOR_TV; - } else if (SDL_strcmp(utf, "tablet") == 0) { - form_factor = SDL_FORMFACTOR_TABLET; - } else if (SDL_strcmp(utf, "phone") == 0) { - form_factor = SDL_FORMFACTOR_PHONE; - } else if (SDL_strcmp(utf, "car") == 0) { - form_factor = SDL_FORMFACTOR_CAR; - } else if (SDL_strcmp(utf, "headset") == 0) { - form_factor = SDL_FORMFACTOR_HEADSET; - } else if (SDL_strcmp(utf, "watch") == 0) { - form_factor = SDL_FORMFACTOR_WATCH; - } else { - form_factor = SDL_FORMFACTOR_UNKNOWN; - } - (*env)->ReleaseStringUTFChars(env, string, utf); - } - (*env)->DeleteLocalRef(env, string); - } - - return form_factor; -} +#endif // !SDL_DIALOG_DISABLED #endif // SDL_PLATFORM_ANDROID diff --git a/src/core/android/SDL_android.h b/src/core/android/SDL_android.h index caa098d32a..b6c6a31438 100644 --- a/src/core/android/SDL_android.h +++ b/src/core/android/SDL_android.h @@ -30,17 +30,17 @@ extern "C" { /* *INDENT-ON* */ #endif -#ifndef SDL_VIDEO_DISABLED -#include -#include -#endif - #ifndef SDL_AUDIO_DISABLED +// Audio support #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 + +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 // Life cycle typedef enum @@ -62,6 +62,9 @@ void Android_UnlockActivityMutex(void); void Android_SetAllowRecreateActivity(bool enabled); #ifndef SDL_VIDEO_DISABLED +#include +#include + // 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); @@ -71,24 +74,34 @@ extern bool Android_JNI_ShouldMinimizeOnFocusLoss(void); extern void Android_JNI_ShowScreenKeyboard(int input_type, SDL_Rect *inputRect); extern void Android_JNI_HideScreenKeyboard(void); +bool Android_JNI_SuspendScreenSaver(bool suspend); 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); +// Also used by the camera subsystem, which is safe because camera requires video. 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 +// Clipboard support +bool Android_JNI_SetClipboardText(const char *text); +char *Android_JNI_GetClipboardText(void); +bool Android_JNI_HasClipboardText(void); -// Detecting device type -extern bool Android_IsDeXMode(void); -extern bool Android_IsChromebook(void); +// Touch support +void Android_JNI_InitTouch(void); + +// Cursor support +int Android_JNI_CreateCustomCursor(SDL_Surface *surface, int hot_x, int hot_y); +void Android_JNI_DestroyCustomCursor(int cursorID); +bool Android_JNI_SetCustomCursor(int cursorID); +bool Android_JNI_SetSystemCursor(int cursorID); + +// Relative mouse support +bool Android_JNI_SupportsRelativeMouse(void); +bool Android_JNI_SetRelativeMouseEnabled(bool enabled); + +// MessageBox +bool Android_JNI_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID); +#endif // !SDL_VIDEO_DISABLED bool Android_JNI_FileOpen(void **puserdata, const char *fileName, const char *mode); Sint64 Android_JNI_FileSize(void *userdata); @@ -103,34 +116,25 @@ 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 - +#ifndef SDL_POWER_DISABLED // Power support int Android_JNI_GetPowerInfo(int *plugged, int *charged, int *battery, int *seconds, int *percent); +#endif // !SDL_POWER_DISABLED +#ifndef SDL_JOYSTICK_DISABLED // Joystick support void Android_JNI_DetectDevices(void); void Android_JNI_JoystickSetLED(int device_id, int red, int green, int blue); void Android_JNI_JoystickSetSensorsEnabled(int device_id, bool enabled); +#endif // !SDL_JOYSTICK_DISABLED +#ifndef SDL_HAPTIC_DISABLED // Haptic support void Android_JNI_DetectHapticDevices(void); void Android_JNI_HapticRun(int device_id, float intensity, int length); void Android_JNI_HapticRumble(int device_id, float low_frequency_intensity, float high_frequency_intensity, int length); 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 +#endif // !SDL_HAPTIC_DISABLED // Threads #include @@ -143,21 +147,6 @@ bool Android_JNI_GetLocale(char *buf, size_t buflen); // Generic messages 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); -bool Android_JNI_SetCustomCursor(int cursorID); -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); @@ -171,10 +160,11 @@ SDL_FormFactor SDL_GetAndroidDeviceFormFactor(void); char *SDL_GetAndroidPackageName(void); // this is a SDL_malloc'd string the caller will own. +#ifndef SDL_DIALOG_DISABLED // File Dialogs -bool Android_JNI_ShowFileDialog(SDL_DialogFileCallback callback, void *userdata, - const SDL_DialogFileFilter *filters, int nfilters, SDL_FileDialogType type, - bool multiple, const char *initialPath); +bool Android_JNI_ShowFileDialog(SDL_DialogFileCallback callback, void *userdata, const SDL_DialogFileFilter *filters, + int nfilters, SDL_FileDialogType type, bool multiple, const char *initialPath); +#endif // !SDL_DIALOG_DISABLED // Ends C function definitions when using C++ #ifdef __cplusplus diff --git a/src/joystick/android/SDL_sysjoystick.c b/src/joystick/android/SDL_sysjoystick.c index 710625edbe..90a2385fcb 100644 --- a/src/joystick/android/SDL_sysjoystick.c +++ b/src/joystick/android/SDL_sysjoystick.c @@ -675,6 +675,7 @@ static bool ANDROID_JoystickOpen(SDL_Joystick *joystick, int device_index) static bool ANDROID_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) { +#ifndef SDL_HAPTIC_DISABLED SDL_joylist_item *item = (SDL_joylist_item *)joystick->hwdata; if (!item) { return SDL_SetError("Rumble failed, device disconnected"); @@ -687,6 +688,9 @@ static bool ANDROID_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_ float high_frequency_intensity = (float)high_frequency_rumble / SDL_MAX_UINT16; Android_JNI_HapticRumble(item->device_id, low_frequency_intensity, high_frequency_intensity, 5000); return true; +#else + return SDL_Unsupported(); +#endif } static bool ANDROID_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)