Simple Directmedia Layer
at main 99 kB view raw
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20*/ 21#include "SDL_internal.h" 22 23#ifdef SDL_PLATFORM_ANDROID 24 25#include "SDL_android.h" 26 27#include "../../events/SDL_events_c.h" 28#include "../../video/android/SDL_androidkeyboard.h" 29#include "../../video/android/SDL_androidmouse.h" 30#include "../../video/android/SDL_androidtouch.h" 31#include "../../video/android/SDL_androidpen.h" 32#include "../../video/android/SDL_androidvideo.h" 33#include "../../video/android/SDL_androidwindow.h" 34#include "../../joystick/android/SDL_sysjoystick_c.h" 35#include "../../haptic/android/SDL_syshaptic_c.h" 36#include "../../hidapi/android/hid.h" 37#include "../../SDL_hints_c.h" 38 39#include <android/log.h> 40#include <android/configuration.h> 41#include <android/asset_manager_jni.h> 42#include <sys/system_properties.h> 43#include <pthread.h> 44#include <sys/types.h> 45#include <unistd.h> 46#include <dlfcn.h> 47 48#define SDL_JAVA_PREFIX org_libsdl_app 49#define CONCAT1(prefix, class, function) CONCAT2(prefix, class, function) 50#define CONCAT2(prefix, class, function) Java_##prefix##_##class##_##function 51#define SDL_JAVA_INTERFACE(function) CONCAT1(SDL_JAVA_PREFIX, SDLActivity, function) 52#define SDL_JAVA_AUDIO_INTERFACE(function) CONCAT1(SDL_JAVA_PREFIX, SDLAudioManager, function) 53#define SDL_JAVA_CONTROLLER_INTERFACE(function) CONCAT1(SDL_JAVA_PREFIX, SDLControllerManager, function) 54#define SDL_JAVA_INTERFACE_INPUT_CONNECTION(function) CONCAT1(SDL_JAVA_PREFIX, SDLInputConnection, function) 55 56// Audio encoding definitions 57#define ENCODING_PCM_8BIT 3 58#define ENCODING_PCM_16BIT 2 59#define ENCODING_PCM_FLOAT 4 60 61// Java class SDLActivity 62JNIEXPORT jstring JNICALL SDL_JAVA_INTERFACE(nativeGetVersion)( 63 JNIEnv *env, jclass cls); 64 65JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)( 66 JNIEnv *env, jclass cls); 67 68JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeInitMainThread)( 69 JNIEnv *env, jclass cls); 70 71JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeCleanupMainThread)( 72 JNIEnv *env, jclass cls); 73 74JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeRunMain)( 75 JNIEnv *env, jclass cls, 76 jstring library, jstring function, jobject array); 77 78JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDropFile)( 79 JNIEnv *env, jclass jcls, 80 jstring filename); 81 82JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetScreenResolution)( 83 JNIEnv *env, jclass jcls, 84 jint surfaceWidth, jint surfaceHeight, 85 jint deviceWidth, jint deviceHeight, jfloat density, jfloat rate); 86 87JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeResize)( 88 JNIEnv *env, jclass cls); 89 90JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceCreated)( 91 JNIEnv *env, jclass jcls); 92 93JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceChanged)( 94 JNIEnv *env, jclass jcls); 95 96JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceDestroyed)( 97 JNIEnv *env, jclass jcls); 98 99JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyDown)( 100 JNIEnv *env, jclass jcls, 101 jint keycode); 102 103JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyUp)( 104 JNIEnv *env, jclass jcls, 105 jint keycode); 106 107JNIEXPORT jboolean JNICALL SDL_JAVA_INTERFACE(onNativeSoftReturnKey)( 108 JNIEnv *env, jclass jcls); 109 110JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyboardFocusLost)( 111 JNIEnv *env, jclass jcls); 112 113JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeTouch)( 114 JNIEnv *env, jclass jcls, 115 jint touch_device_id_in, jint pointer_finger_id_in, 116 jint action, jfloat x, jfloat y, jfloat p); 117 118JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeMouse)( 119 JNIEnv *env, jclass jcls, 120 jint button, jint action, jfloat x, jfloat y, jboolean relative); 121 122JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePen)( 123 JNIEnv *env, jclass jcls, 124 jint pen_id_in, jint button, jint action, jfloat x, jfloat y, jfloat p); 125 126JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeAccel)( 127 JNIEnv *env, jclass jcls, 128 jfloat x, jfloat y, jfloat z); 129 130JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeClipboardChanged)( 131 JNIEnv *env, jclass jcls); 132 133JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeLowMemory)( 134 JNIEnv *env, jclass cls); 135 136JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeLocaleChanged)( 137 JNIEnv *env, jclass cls); 138 139JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDarkModeChanged)( 140 JNIEnv *env, jclass cls, jboolean enabled); 141 142JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSendQuit)( 143 JNIEnv *env, jclass cls); 144 145JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeQuit)( 146 JNIEnv *env, jclass cls); 147 148JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativePause)( 149 JNIEnv *env, jclass cls); 150 151JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeResume)( 152 JNIEnv *env, jclass cls); 153 154JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeFocusChanged)( 155 JNIEnv *env, jclass cls, jboolean hasFocus); 156 157JNIEXPORT jstring JNICALL SDL_JAVA_INTERFACE(nativeGetHint)( 158 JNIEnv *env, jclass cls, 159 jstring name); 160 161JNIEXPORT jboolean JNICALL SDL_JAVA_INTERFACE(nativeGetHintBoolean)( 162 JNIEnv *env, jclass cls, 163 jstring name, jboolean default_value); 164 165JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetenv)( 166 JNIEnv *env, jclass cls, 167 jstring name, jstring value); 168 169JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetNaturalOrientation)( 170 JNIEnv *env, jclass cls, 171 jint orientation); 172 173JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeRotationChanged)( 174 JNIEnv *env, jclass cls, 175 jint rotation); 176 177JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeInsetsChanged)( 178 JNIEnv *env, jclass cls, 179 jint left, jint right, jint top, jint bottom); 180 181JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeAddTouch)( 182 JNIEnv *env, jclass cls, 183 jint touchId, jstring name); 184 185JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativePermissionResult)( 186 JNIEnv *env, jclass cls, 187 jint requestCode, jboolean result); 188 189JNIEXPORT jboolean JNICALL SDL_JAVA_INTERFACE(nativeAllowRecreateActivity)( 190 JNIEnv *env, jclass jcls); 191 192JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeCheckSDLThreadCounter)( 193 JNIEnv *env, jclass jcls); 194 195JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeFileDialog)( 196 JNIEnv *env, jclass jcls, 197 jint requestCode, jobjectArray fileList, jint filter); 198 199static JNINativeMethod SDLActivity_tab[] = { 200 { "nativeGetVersion", "()Ljava/lang/String;", SDL_JAVA_INTERFACE(nativeGetVersion) }, 201 { "nativeSetupJNI", "()I", SDL_JAVA_INTERFACE(nativeSetupJNI) }, 202 { "nativeInitMainThread", "()V", SDL_JAVA_INTERFACE(nativeInitMainThread) }, 203 { "nativeCleanupMainThread", "()V", SDL_JAVA_INTERFACE(nativeCleanupMainThread) }, 204 { "nativeRunMain", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)I", SDL_JAVA_INTERFACE(nativeRunMain) }, 205 { "onNativeDropFile", "(Ljava/lang/String;)V", SDL_JAVA_INTERFACE(onNativeDropFile) }, 206 { "nativeSetScreenResolution", "(IIIIFF)V", SDL_JAVA_INTERFACE(nativeSetScreenResolution) }, 207 { "onNativeResize", "()V", SDL_JAVA_INTERFACE(onNativeResize) }, 208 { "onNativeSurfaceCreated", "()V", SDL_JAVA_INTERFACE(onNativeSurfaceCreated) }, 209 { "onNativeSurfaceChanged", "()V", SDL_JAVA_INTERFACE(onNativeSurfaceChanged) }, 210 { "onNativeSurfaceDestroyed", "()V", SDL_JAVA_INTERFACE(onNativeSurfaceDestroyed) }, 211 { "onNativeKeyDown", "(I)V", SDL_JAVA_INTERFACE(onNativeKeyDown) }, 212 { "onNativeKeyUp", "(I)V", SDL_JAVA_INTERFACE(onNativeKeyUp) }, 213 { "onNativeSoftReturnKey", "()Z", SDL_JAVA_INTERFACE(onNativeSoftReturnKey) }, 214 { "onNativeKeyboardFocusLost", "()V", SDL_JAVA_INTERFACE(onNativeKeyboardFocusLost) }, 215 { "onNativeTouch", "(IIIFFF)V", SDL_JAVA_INTERFACE(onNativeTouch) }, 216 { "onNativeMouse", "(IIFFZ)V", SDL_JAVA_INTERFACE(onNativeMouse) }, 217 { "onNativePen", "(IIIFFF)V", SDL_JAVA_INTERFACE(onNativePen) }, 218 { "onNativeAccel", "(FFF)V", SDL_JAVA_INTERFACE(onNativeAccel) }, 219 { "onNativeClipboardChanged", "()V", SDL_JAVA_INTERFACE(onNativeClipboardChanged) }, 220 { "nativeLowMemory", "()V", SDL_JAVA_INTERFACE(nativeLowMemory) }, 221 { "onNativeLocaleChanged", "()V", SDL_JAVA_INTERFACE(onNativeLocaleChanged) }, 222 { "onNativeDarkModeChanged", "(Z)V", SDL_JAVA_INTERFACE(onNativeDarkModeChanged) }, 223 { "nativeSendQuit", "()V", SDL_JAVA_INTERFACE(nativeSendQuit) }, 224 { "nativeQuit", "()V", SDL_JAVA_INTERFACE(nativeQuit) }, 225 { "nativePause", "()V", SDL_JAVA_INTERFACE(nativePause) }, 226 { "nativeResume", "()V", SDL_JAVA_INTERFACE(nativeResume) }, 227 { "nativeFocusChanged", "(Z)V", SDL_JAVA_INTERFACE(nativeFocusChanged) }, 228 { "nativeGetHint", "(Ljava/lang/String;)Ljava/lang/String;", SDL_JAVA_INTERFACE(nativeGetHint) }, 229 { "nativeGetHintBoolean", "(Ljava/lang/String;Z)Z", SDL_JAVA_INTERFACE(nativeGetHintBoolean) }, 230 { "nativeSetenv", "(Ljava/lang/String;Ljava/lang/String;)V", SDL_JAVA_INTERFACE(nativeSetenv) }, 231 { "nativeSetNaturalOrientation", "(I)V", SDL_JAVA_INTERFACE(nativeSetNaturalOrientation) }, 232 { "onNativeRotationChanged", "(I)V", SDL_JAVA_INTERFACE(onNativeRotationChanged) }, 233 { "onNativeInsetsChanged", "(IIII)V", SDL_JAVA_INTERFACE(onNativeInsetsChanged) }, 234 { "nativeAddTouch", "(ILjava/lang/String;)V", SDL_JAVA_INTERFACE(nativeAddTouch) }, 235 { "nativePermissionResult", "(IZ)V", SDL_JAVA_INTERFACE(nativePermissionResult) }, 236 { "nativeAllowRecreateActivity", "()Z", SDL_JAVA_INTERFACE(nativeAllowRecreateActivity) }, 237 { "nativeCheckSDLThreadCounter", "()I", SDL_JAVA_INTERFACE(nativeCheckSDLThreadCounter) }, 238 { "onNativeFileDialog", "(I[Ljava/lang/String;I)V", SDL_JAVA_INTERFACE(onNativeFileDialog) } 239}; 240 241// Java class SDLInputConnection 242JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeCommitText)( 243 JNIEnv *env, jclass cls, 244 jstring text, jint newCursorPosition); 245 246JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeGenerateScancodeForUnichar)( 247 JNIEnv *env, jclass cls, 248 jchar chUnicode); 249 250static JNINativeMethod SDLInputConnection_tab[] = { 251 { "nativeCommitText", "(Ljava/lang/String;I)V", SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeCommitText) }, 252 { "nativeGenerateScancodeForUnichar", "(C)V", SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeGenerateScancodeForUnichar) } 253}; 254 255// Java class SDLAudioManager 256JNIEXPORT void JNICALL SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI)( 257 JNIEnv *env, jclass jcls); 258 259JNIEXPORT void JNICALL 260 SDL_JAVA_AUDIO_INTERFACE(addAudioDevice)(JNIEnv *env, jclass jcls, jboolean recording, jstring name, 261 jint device_id); 262 263JNIEXPORT void JNICALL 264 SDL_JAVA_AUDIO_INTERFACE(removeAudioDevice)(JNIEnv *env, jclass jcls, jboolean recording, 265 jint device_id); 266 267static JNINativeMethod SDLAudioManager_tab[] = { 268 { "nativeSetupJNI", "()I", SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI) }, 269 { "addAudioDevice", "(ZLjava/lang/String;I)V", SDL_JAVA_AUDIO_INTERFACE(addAudioDevice) }, 270 { "removeAudioDevice", "(ZI)V", SDL_JAVA_AUDIO_INTERFACE(removeAudioDevice) } 271}; 272 273// Java class SDLControllerManager 274JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI)( 275 JNIEnv *env, jclass jcls); 276 277JNIEXPORT jboolean JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadDown)( 278 JNIEnv *env, jclass jcls, 279 jint device_id, jint keycode); 280 281JNIEXPORT jboolean JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadUp)( 282 JNIEnv *env, jclass jcls, 283 jint device_id, jint keycode); 284 285JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeJoy)( 286 JNIEnv *env, jclass jcls, 287 jint device_id, jint axis, jfloat value); 288 289JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeHat)( 290 JNIEnv *env, jclass jcls, 291 jint device_id, jint hat_id, jint x, jint y); 292 293JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick)( 294 JNIEnv *env, jclass jcls, 295 jint device_id, jstring device_name, jstring device_desc, jint vendor_id, jint product_id, 296 jint button_mask, jint naxes, jint axis_mask, jint nhats, jboolean can_rumble); 297 298JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveJoystick)( 299 JNIEnv *env, jclass jcls, 300 jint device_id); 301 302JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddHaptic)( 303 JNIEnv *env, jclass jcls, 304 jint device_id, jstring device_name); 305 306JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveHaptic)( 307 JNIEnv *env, jclass jcls, 308 jint device_id); 309 310static JNINativeMethod SDLControllerManager_tab[] = { 311 { "nativeSetupJNI", "()I", SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI) }, 312 { "onNativePadDown", "(II)Z", SDL_JAVA_CONTROLLER_INTERFACE(onNativePadDown) }, 313 { "onNativePadUp", "(II)Z", SDL_JAVA_CONTROLLER_INTERFACE(onNativePadUp) }, 314 { "onNativeJoy", "(IIF)V", SDL_JAVA_CONTROLLER_INTERFACE(onNativeJoy) }, 315 { "onNativeHat", "(IIII)V", SDL_JAVA_CONTROLLER_INTERFACE(onNativeHat) }, 316 { "nativeAddJoystick", "(ILjava/lang/String;Ljava/lang/String;IIIIIIZ)V", SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick) }, 317 { "nativeRemoveJoystick", "(I)V", SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveJoystick) }, 318 { "nativeAddHaptic", "(ILjava/lang/String;)V", SDL_JAVA_CONTROLLER_INTERFACE(nativeAddHaptic) }, 319 { "nativeRemoveHaptic", "(I)V", SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveHaptic) } 320}; 321 322// Uncomment this to log messages entering and exiting methods in this file 323// #define DEBUG_JNI 324 325static void checkJNIReady(void); 326 327/******************************************************************************* 328 This file links the Java side of Android with libsdl 329*******************************************************************************/ 330#include <jni.h> 331 332/******************************************************************************* 333 Globals 334*******************************************************************************/ 335static pthread_key_t mThreadKey; 336static pthread_once_t key_once = PTHREAD_ONCE_INIT; 337static JavaVM *mJavaVM = NULL; 338 339// Main activity 340static jclass mActivityClass; 341 342// method signatures 343static jmethodID midClipboardGetText; 344static jmethodID midClipboardHasText; 345static jmethodID midClipboardSetText; 346static jmethodID midCreateCustomCursor; 347static jmethodID midDestroyCustomCursor; 348static jmethodID midGetContext; 349static jmethodID midGetManifestEnvironmentVariables; 350static jmethodID midGetNativeSurface; 351static jmethodID midInitTouch; 352static jmethodID midIsAndroidTV; 353static jmethodID midIsChromebook; 354static jmethodID midIsDeXMode; 355static jmethodID midIsScreenKeyboardShown; 356static jmethodID midIsTablet; 357static jmethodID midManualBackButton; 358static jmethodID midMinimizeWindow; 359static jmethodID midOpenURL; 360static jmethodID midRequestPermission; 361static jmethodID midShowToast; 362static jmethodID midSendMessage; 363static jmethodID midSetActivityTitle; 364static jmethodID midSetCustomCursor; 365static jmethodID midSetOrientation; 366static jmethodID midSetRelativeMouseEnabled; 367static jmethodID midSetSystemCursor; 368static jmethodID midSetWindowStyle; 369static jmethodID midShouldMinimizeOnFocusLoss; 370static jmethodID midShowTextInput; 371static jmethodID midSupportsRelativeMouse; 372static jmethodID midOpenFileDescriptor; 373static jmethodID midShowFileDialog; 374 375// audio manager 376static jclass mAudioManagerClass; 377 378// method signatures 379static jmethodID midRegisterAudioDeviceCallback; 380static jmethodID midUnregisterAudioDeviceCallback; 381static jmethodID midAudioSetThreadPriority; 382 383// controller manager 384static jclass mControllerManagerClass; 385 386// method signatures 387static jmethodID midPollInputDevices; 388static jmethodID midPollHapticDevices; 389static jmethodID midHapticRun; 390static jmethodID midHapticRumble; 391static jmethodID midHapticStop; 392 393// Accelerometer data storage 394static SDL_DisplayOrientation displayNaturalOrientation; 395static SDL_DisplayOrientation displayCurrentOrientation; 396static float fLastAccelerometer[3]; 397static bool bHasNewData; 398 399static bool bHasEnvironmentVariables; 400 401// Android AssetManager 402static void Internal_Android_Create_AssetManager(void); 403static void Internal_Android_Destroy_AssetManager(void); 404static AAssetManager *asset_manager = NULL; 405static jobject javaAssetManagerRef = 0; 406 407// Re-create activity hint 408static SDL_AtomicInt bAllowRecreateActivity; 409 410static SDL_Mutex *Android_ActivityMutex = NULL; 411static SDL_Mutex *Android_LifecycleMutex = NULL; 412static SDL_Semaphore *Android_LifecycleEventSem = NULL; 413static SDL_AndroidLifecycleEvent Android_LifecycleEvents[SDL_NUM_ANDROID_LIFECYCLE_EVENTS]; 414static int Android_NumLifecycleEvents; 415 416/******************************************************************************* 417 Functions called by JNI 418*******************************************************************************/ 419 420/* From http://developer.android.com/guide/practices/jni.html 421 * All threads are Linux threads, scheduled by the kernel. 422 * They're usually started from managed code (using Thread.start), but they can also be created elsewhere and then 423 * attached to the JavaVM. For example, a thread started with pthread_create can be attached with the 424 * JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a thread is attached, it has no JNIEnv, 425 * and cannot make JNI calls. 426 * Attaching a natively-created thread causes a java.lang.Thread object to be constructed and added to the "main" 427 * ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread on an already-attached thread 428 * is a no-op. 429 * Note: You can call this function any number of times for the same thread, there's no harm in it 430 */ 431 432/* From http://developer.android.com/guide/practices/jni.html 433 * Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward, 434 * in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be 435 * called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific 436 * to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.) 437 * Note: The destructor is not called unless the stored value is != NULL 438 * Note: You can call this function any number of times for the same thread, there's no harm in it 439 * (except for some lost CPU cycles) 440 */ 441 442// Set local storage value 443static bool Android_JNI_SetEnv(JNIEnv *env) 444{ 445 int status = pthread_setspecific(mThreadKey, env); 446 if (status < 0) { 447 __android_log_print(ANDROID_LOG_ERROR, "SDL", "Failed pthread_setspecific() in Android_JNI_SetEnv() (err=%d)", status); 448 return false; 449 } 450 return true; 451} 452 453// Get local storage value 454JNIEnv *Android_JNI_GetEnv(void) 455{ 456 // Get JNIEnv from the Thread local storage 457 JNIEnv *env = pthread_getspecific(mThreadKey); 458 if (!env) { 459 // If it fails, try to attach ! (e.g the thread isn't created with SDL_CreateThread() 460 int status; 461 462 // There should be a JVM 463 if (!mJavaVM) { 464 __android_log_print(ANDROID_LOG_ERROR, "SDL", "Failed, there is no JavaVM"); 465 return NULL; 466 } 467 468 /* Attach the current thread to the JVM and get a JNIEnv. 469 * It will be detached by pthread_create destructor 'Android_JNI_ThreadDestroyed' */ 470 status = (*mJavaVM)->AttachCurrentThread(mJavaVM, &env, NULL); 471 if (status < 0) { 472 __android_log_print(ANDROID_LOG_ERROR, "SDL", "Failed to attach current thread (err=%d)", status); 473 return NULL; 474 } 475 476 // Save JNIEnv into the Thread local storage 477 if (!Android_JNI_SetEnv(env)) { 478 return NULL; 479 } 480 } 481 482 return env; 483} 484 485// Set up an external thread for using JNI with Android_JNI_GetEnv() 486bool Android_JNI_SetupThread(void) 487{ 488 JNIEnv *env; 489 int status; 490 491 // There should be a JVM 492 if (!mJavaVM) { 493 __android_log_print(ANDROID_LOG_ERROR, "SDL", "Failed, there is no JavaVM"); 494 return false; 495 } 496 497 /* Attach the current thread to the JVM and get a JNIEnv. 498 * It will be detached by pthread_create destructor 'Android_JNI_ThreadDestroyed' */ 499 status = (*mJavaVM)->AttachCurrentThread(mJavaVM, &env, NULL); 500 if (status < 0) { 501 __android_log_print(ANDROID_LOG_ERROR, "SDL", "Failed to attach current thread (err=%d)", status); 502 return false; 503 } 504 505 // Save JNIEnv into the Thread local storage 506 if (!Android_JNI_SetEnv(env)) { 507 return false; 508 } 509 510 return true; 511} 512 513// Destructor called for each thread where mThreadKey is not NULL 514static void Android_JNI_ThreadDestroyed(void *value) 515{ 516 // The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required 517 JNIEnv *env = (JNIEnv *)value; 518 if (env) { 519 (*mJavaVM)->DetachCurrentThread(mJavaVM); 520 Android_JNI_SetEnv(NULL); 521 } 522} 523 524// Creation of local storage mThreadKey 525static void Android_JNI_CreateKey(void) 526{ 527 int status = pthread_key_create(&mThreadKey, Android_JNI_ThreadDestroyed); 528 if (status < 0) { 529 __android_log_print(ANDROID_LOG_ERROR, "SDL", "Error initializing mThreadKey with pthread_key_create() (err=%d)", status); 530 } 531} 532 533static void Android_JNI_CreateKey_once(void) 534{ 535 int status = pthread_once(&key_once, Android_JNI_CreateKey); 536 if (status < 0) { 537 __android_log_print(ANDROID_LOG_ERROR, "SDL", "Error initializing mThreadKey with pthread_once() (err=%d)", status); 538 } 539} 540 541static void register_methods(JNIEnv *env, const char *classname, JNINativeMethod *methods, int nb) 542{ 543 jclass clazz = (*env)->FindClass(env, classname); 544 if (!clazz || (*env)->RegisterNatives(env, clazz, methods, nb) < 0) { 545 __android_log_print(ANDROID_LOG_ERROR, "SDL", "Failed to register methods of %s", classname); 546 return; 547 } 548} 549 550// Library init 551JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) 552{ 553 JNIEnv *env = NULL; 554 555 mJavaVM = vm; 556 557 if ((*mJavaVM)->GetEnv(mJavaVM, (void **)&env, JNI_VERSION_1_4) != JNI_OK) { 558 __android_log_print(ANDROID_LOG_ERROR, "SDL", "Failed to get JNI Env"); 559 return JNI_VERSION_1_4; 560 } 561 562 register_methods(env, "org/libsdl/app/SDLActivity", SDLActivity_tab, SDL_arraysize(SDLActivity_tab)); 563 register_methods(env, "org/libsdl/app/SDLInputConnection", SDLInputConnection_tab, SDL_arraysize(SDLInputConnection_tab)); 564 register_methods(env, "org/libsdl/app/SDLAudioManager", SDLAudioManager_tab, SDL_arraysize(SDLAudioManager_tab)); 565 register_methods(env, "org/libsdl/app/SDLControllerManager", SDLControllerManager_tab, SDL_arraysize(SDLControllerManager_tab)); 566 register_methods(env, "org/libsdl/app/HIDDeviceManager", HIDDeviceManager_tab, SDL_arraysize(HIDDeviceManager_tab)); 567 SDL_SetAtomicInt(&bAllowRecreateActivity, false); 568 569 return JNI_VERSION_1_4; 570} 571 572void checkJNIReady(void) 573{ 574 if (!mActivityClass || !mAudioManagerClass || !mControllerManagerClass) { 575 // We aren't fully initialized, let's just return. 576 return; 577 } 578 579 SDL_SetMainReady(); 580} 581 582// Get SDL version -- called before SDL_main() to verify JNI bindings 583JNIEXPORT jstring JNICALL SDL_JAVA_INTERFACE(nativeGetVersion)(JNIEnv *env, jclass cls) 584{ 585 char version[128]; 586 587 SDL_snprintf(version, sizeof(version), "%d.%d.%d", SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_MICRO_VERSION); 588 589 return (*env)->NewStringUTF(env, version); 590} 591 592// Activity initialization -- called before SDL_main() to initialize JNI bindings 593JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cls) 594{ 595 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeSetupJNI()"); 596 597 // Start with a clean slate 598 SDL_ClearError(); 599 600 /* 601 * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread 602 * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this 603 */ 604 Android_JNI_CreateKey_once(); 605 606 // Save JNIEnv of SDLActivity 607 Android_JNI_SetEnv(env); 608 609 if (!mJavaVM) { 610 __android_log_print(ANDROID_LOG_ERROR, "SDL", "failed to found a JavaVM"); 611 } 612 613 /* Use a mutex to prevent concurrency issues between Java Activity and Native thread code, when using 'Android_Window'. 614 * (Eg. Java sending Touch events, while native code is destroying the main SDL_Window. ) 615 */ 616 if (!Android_ActivityMutex) { 617 Android_ActivityMutex = SDL_CreateMutex(); // Could this be created twice if onCreate() is called a second time ? 618 } 619 620 if (!Android_ActivityMutex) { 621 __android_log_print(ANDROID_LOG_ERROR, "SDL", "failed to create Android_ActivityMutex mutex"); 622 } 623 624 Android_LifecycleMutex = SDL_CreateMutex(); 625 if (!Android_LifecycleMutex) { 626 __android_log_print(ANDROID_LOG_ERROR, "SDL", "failed to create Android_LifecycleMutex mutex"); 627 } 628 629 Android_LifecycleEventSem = SDL_CreateSemaphore(0); 630 if (!Android_LifecycleEventSem) { 631 __android_log_print(ANDROID_LOG_ERROR, "SDL", "failed to create Android_LifecycleEventSem semaphore"); 632 } 633 634 mActivityClass = (jclass)((*env)->NewGlobalRef(env, cls)); 635 636 midClipboardGetText = (*env)->GetStaticMethodID(env, mActivityClass, "clipboardGetText", "()Ljava/lang/String;"); 637 midClipboardHasText = (*env)->GetStaticMethodID(env, mActivityClass, "clipboardHasText", "()Z"); 638 midClipboardSetText = (*env)->GetStaticMethodID(env, mActivityClass, "clipboardSetText", "(Ljava/lang/String;)V"); 639 midCreateCustomCursor = (*env)->GetStaticMethodID(env, mActivityClass, "createCustomCursor", "([IIIII)I"); 640 midDestroyCustomCursor = (*env)->GetStaticMethodID(env, mActivityClass, "destroyCustomCursor", "(I)V"); 641 midGetContext = (*env)->GetStaticMethodID(env, mActivityClass, "getContext", "()Landroid/content/Context;"); 642 midGetManifestEnvironmentVariables = (*env)->GetStaticMethodID(env, mActivityClass, "getManifestEnvironmentVariables", "()Z"); 643 midGetNativeSurface = (*env)->GetStaticMethodID(env, mActivityClass, "getNativeSurface", "()Landroid/view/Surface;"); 644 midInitTouch = (*env)->GetStaticMethodID(env, mActivityClass, "initTouch", "()V"); 645 midIsAndroidTV = (*env)->GetStaticMethodID(env, mActivityClass, "isAndroidTV", "()Z"); 646 midIsChromebook = (*env)->GetStaticMethodID(env, mActivityClass, "isChromebook", "()Z"); 647 midIsDeXMode = (*env)->GetStaticMethodID(env, mActivityClass, "isDeXMode", "()Z"); 648 midIsScreenKeyboardShown = (*env)->GetStaticMethodID(env, mActivityClass, "isScreenKeyboardShown", "()Z"); 649 midIsTablet = (*env)->GetStaticMethodID(env, mActivityClass, "isTablet", "()Z"); 650 midManualBackButton = (*env)->GetStaticMethodID(env, mActivityClass, "manualBackButton", "()V"); 651 midMinimizeWindow = (*env)->GetStaticMethodID(env, mActivityClass, "minimizeWindow", "()V"); 652 midOpenURL = (*env)->GetStaticMethodID(env, mActivityClass, "openURL", "(Ljava/lang/String;)Z"); 653 midRequestPermission = (*env)->GetStaticMethodID(env, mActivityClass, "requestPermission", "(Ljava/lang/String;I)V"); 654 midShowToast = (*env)->GetStaticMethodID(env, mActivityClass, "showToast", "(Ljava/lang/String;IIII)Z"); 655 midSendMessage = (*env)->GetStaticMethodID(env, mActivityClass, "sendMessage", "(II)Z"); 656 midSetActivityTitle = (*env)->GetStaticMethodID(env, mActivityClass, "setActivityTitle", "(Ljava/lang/String;)Z"); 657 midSetCustomCursor = (*env)->GetStaticMethodID(env, mActivityClass, "setCustomCursor", "(I)Z"); 658 midSetOrientation = (*env)->GetStaticMethodID(env, mActivityClass, "setOrientation", "(IIZLjava/lang/String;)V"); 659 midSetRelativeMouseEnabled = (*env)->GetStaticMethodID(env, mActivityClass, "setRelativeMouseEnabled", "(Z)Z"); 660 midSetSystemCursor = (*env)->GetStaticMethodID(env, mActivityClass, "setSystemCursor", "(I)Z"); 661 midSetWindowStyle = (*env)->GetStaticMethodID(env, mActivityClass, "setWindowStyle", "(Z)V"); 662 midShouldMinimizeOnFocusLoss = (*env)->GetStaticMethodID(env, mActivityClass, "shouldMinimizeOnFocusLoss", "()Z"); 663 midShowTextInput = (*env)->GetStaticMethodID(env, mActivityClass, "showTextInput", "(IIIII)Z"); 664 midSupportsRelativeMouse = (*env)->GetStaticMethodID(env, mActivityClass, "supportsRelativeMouse", "()Z"); 665 midOpenFileDescriptor = (*env)->GetStaticMethodID(env, mActivityClass, "openFileDescriptor", "(Ljava/lang/String;Ljava/lang/String;)I"); 666 midShowFileDialog = (*env)->GetStaticMethodID(env, mActivityClass, "showFileDialog", "([Ljava/lang/String;ZZI)Z"); 667 668 if (!midClipboardGetText || 669 !midClipboardHasText || 670 !midClipboardSetText || 671 !midCreateCustomCursor || 672 !midDestroyCustomCursor || 673 !midGetContext || 674 !midGetManifestEnvironmentVariables || 675 !midGetNativeSurface || 676 !midInitTouch || 677 !midIsAndroidTV || 678 !midIsChromebook || 679 !midIsDeXMode || 680 !midIsScreenKeyboardShown || 681 !midIsTablet || 682 !midManualBackButton || 683 !midMinimizeWindow || 684 !midOpenURL || 685 !midRequestPermission || 686 !midShowToast || 687 !midSendMessage || 688 !midSetActivityTitle || 689 !midSetCustomCursor || 690 !midSetOrientation || 691 !midSetRelativeMouseEnabled || 692 !midSetSystemCursor || 693 !midSetWindowStyle || 694 !midShouldMinimizeOnFocusLoss || 695 !midShowTextInput || 696 !midSupportsRelativeMouse || 697 !midOpenFileDescriptor || 698 !midShowFileDialog) { 699 __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLActivity.java?"); 700 } 701 702 checkJNIReady(); 703} 704 705// Audio initialization -- called before SDL_main() to initialize JNI bindings 706JNIEXPORT void JNICALL SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cls) 707{ 708 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "AUDIO nativeSetupJNI()"); 709 710 mAudioManagerClass = (jclass)((*env)->NewGlobalRef(env, cls)); 711 712 midRegisterAudioDeviceCallback = (*env)->GetStaticMethodID(env, mAudioManagerClass, 713 "registerAudioDeviceCallback", 714 "()V"); 715 midUnregisterAudioDeviceCallback = (*env)->GetStaticMethodID(env, mAudioManagerClass, 716 "unregisterAudioDeviceCallback", 717 "()V"); 718 midAudioSetThreadPriority = (*env)->GetStaticMethodID(env, mAudioManagerClass, 719 "audioSetThreadPriority", "(ZI)V"); 720 721 if (!midRegisterAudioDeviceCallback || !midUnregisterAudioDeviceCallback || !midAudioSetThreadPriority) { 722 __android_log_print(ANDROID_LOG_WARN, "SDL", 723 "Missing some Java callbacks, do you have the latest version of SDLAudioManager.java?"); 724 } 725 726 checkJNIReady(); 727} 728 729// Controller initialization -- called before SDL_main() to initialize JNI bindings 730JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cls) 731{ 732 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "CONTROLLER nativeSetupJNI()"); 733 734 mControllerManagerClass = (jclass)((*env)->NewGlobalRef(env, cls)); 735 736 midPollInputDevices = (*env)->GetStaticMethodID(env, mControllerManagerClass, 737 "pollInputDevices", "()V"); 738 midPollHapticDevices = (*env)->GetStaticMethodID(env, mControllerManagerClass, 739 "pollHapticDevices", "()V"); 740 midHapticRun = (*env)->GetStaticMethodID(env, mControllerManagerClass, 741 "hapticRun", "(IFI)V"); 742 midHapticRumble = (*env)->GetStaticMethodID(env, mControllerManagerClass, 743 "hapticRumble", "(IFFI)V"); 744 midHapticStop = (*env)->GetStaticMethodID(env, mControllerManagerClass, 745 "hapticStop", "(I)V"); 746 747 if (!midPollInputDevices || !midPollHapticDevices || !midHapticRun || !midHapticRumble || !midHapticStop) { 748 __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLControllerManager.java?"); 749 } 750 751 checkJNIReady(); 752} 753 754// SDL main function prototype 755typedef int (*SDL_main_func)(int argc, char *argv[]); 756 757static int run_count = 0; 758 759JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeCheckSDLThreadCounter)( 760 JNIEnv *env, jclass jcls) 761{ 762 int tmp = run_count; 763 run_count += 1; 764 return tmp; 765} 766 767static void SDLCALL SDL_AllowRecreateActivityChanged(void *userdata, const char *name, const char *oldValue, const char *hint) 768{ 769 if (SDL_GetStringBoolean(hint, false)) { 770 SDL_SetAtomicInt(&bAllowRecreateActivity, true); 771 } else { 772 SDL_SetAtomicInt(&bAllowRecreateActivity, false); 773 } 774} 775 776JNIEXPORT jboolean JNICALL SDL_JAVA_INTERFACE(nativeAllowRecreateActivity)( 777 JNIEnv *env, jclass jcls) 778{ 779 return SDL_GetAtomicInt(&bAllowRecreateActivity); 780} 781 782JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeInitMainThread)( 783 JNIEnv *env, jclass jcls) 784{ 785 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeInitSDLThread() %d time", run_count); 786 if (run_count == 1) { 787 SDL_AddHintCallback(SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY, SDL_AllowRecreateActivityChanged, NULL); 788 } 789 run_count += 1; 790 791 // Save JNIEnv of SDLThread 792 Android_JNI_SetEnv(env); 793} 794 795JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeCleanupMainThread)( 796 JNIEnv *env, jclass jcls) 797{ 798 /* This is a Java thread, it doesn't need to be Detached from the JVM. 799 * Set to mThreadKey value to NULL not to call pthread_create destructor 'Android_JNI_ThreadDestroyed' */ 800 Android_JNI_SetEnv(NULL); 801} 802 803// Start up the SDL app 804JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeRunMain)(JNIEnv *env, jclass cls, jstring library, jstring function, jobject array) 805{ 806 int status = -1; 807 const char *library_file; 808 void *library_handle; 809 810 library_file = (*env)->GetStringUTFChars(env, library, NULL); 811 library_handle = dlopen(library_file, RTLD_GLOBAL); 812 813 if (library_handle == NULL) { 814 /* When deploying android app bundle format uncompressed native libs may not extract from apk to filesystem. 815 In this case we should use lib name without path. https://bugzilla.libsdl.org/show_bug.cgi?id=4739 */ 816 const char *library_name = SDL_strrchr(library_file, '/'); 817 if (library_name && *library_name) { 818 library_name += 1; 819 library_handle = dlopen(library_name, RTLD_GLOBAL); 820 } 821 } 822 823 if (library_handle) { 824 const char *function_name; 825 SDL_main_func SDL_main; 826 827 function_name = (*env)->GetStringUTFChars(env, function, NULL); 828 SDL_main = (SDL_main_func)dlsym(library_handle, function_name); 829 if (SDL_main) { 830 int i; 831 int argc; 832 int len; 833 char **argv; 834 bool isstack; 835 836 // Prepare the arguments. 837 len = (*env)->GetArrayLength(env, array); 838 argv = SDL_small_alloc(char *, 1 + len + 1, &isstack); // !!! FIXME: check for NULL 839 argc = 0; 840 /* Use the name "app_process" so PHYSFS_platformCalcBaseDir() works. 841 https://github.com/love2d/love-android/issues/24 842 */ 843 argv[argc++] = SDL_strdup("app_process"); 844 for (i = 0; i < len; ++i) { 845 char *arg = NULL; 846 jstring string = (*env)->GetObjectArrayElement(env, array, i); 847 if (string) { 848 const char *utf = (*env)->GetStringUTFChars(env, string, 0); 849 if (utf) { 850 arg = SDL_strdup(utf); 851 (*env)->ReleaseStringUTFChars(env, string, utf); 852 } 853 (*env)->DeleteLocalRef(env, string); 854 } 855 if (arg == NULL) { 856 arg = SDL_strdup(""); 857 } 858 argv[argc++] = arg; 859 } 860 argv[argc] = NULL; 861 862 // Run the application. 863 status = SDL_main(argc, argv); 864 865 // Release the arguments. 866 for (i = 0; i < argc; ++i) { 867 SDL_free(argv[i]); 868 } 869 SDL_small_free(argv, isstack); 870 871 } else { 872 __android_log_print(ANDROID_LOG_ERROR, "SDL", "nativeRunMain(): Couldn't find function %s in library %s", function_name, library_file); 873 } 874 (*env)->ReleaseStringUTFChars(env, function, function_name); 875 876 dlclose(library_handle); 877 878 } else { 879 __android_log_print(ANDROID_LOG_ERROR, "SDL", "nativeRunMain(): Couldn't load library %s", library_file); 880 } 881 (*env)->ReleaseStringUTFChars(env, library, library_file); 882 883 // Do not issue an exit or the whole application will terminate instead of just the SDL thread 884 // exit(status); 885 886 return status; 887} 888 889static int FindLifecycleEvent(SDL_AndroidLifecycleEvent event) 890{ 891 for (int index = 0; index < Android_NumLifecycleEvents; ++index) { 892 if (Android_LifecycleEvents[index] == event) { 893 return index; 894 } 895 } 896 return -1; 897} 898 899static void RemoveLifecycleEvent(int index) 900{ 901 if (index < Android_NumLifecycleEvents - 1) { 902 SDL_memcpy(&Android_LifecycleEvents[index], &Android_LifecycleEvents[index+1], (Android_NumLifecycleEvents - index - 1) * sizeof(Android_LifecycleEvents[index])); 903 } 904 --Android_NumLifecycleEvents; 905} 906 907void Android_SendLifecycleEvent(SDL_AndroidLifecycleEvent event) 908{ 909 SDL_LockMutex(Android_LifecycleMutex); 910 { 911 int index; 912 bool add_event = true; 913 914 switch (event) { 915 case SDL_ANDROID_LIFECYCLE_WAKE: 916 // We don't need more than one wake queued 917 index = FindLifecycleEvent(SDL_ANDROID_LIFECYCLE_WAKE); 918 if (index >= 0) { 919 add_event = false; 920 } 921 break; 922 case SDL_ANDROID_LIFECYCLE_PAUSE: 923 // If we have a resume queued, just stay in the paused state 924 index = FindLifecycleEvent(SDL_ANDROID_LIFECYCLE_RESUME); 925 if (index >= 0) { 926 RemoveLifecycleEvent(index); 927 add_event = false; 928 } 929 break; 930 case SDL_ANDROID_LIFECYCLE_RESUME: 931 // If we have a pause queued, just stay in the resumed state 932 index = FindLifecycleEvent(SDL_ANDROID_LIFECYCLE_PAUSE); 933 if (index >= 0) { 934 RemoveLifecycleEvent(index); 935 add_event = false; 936 } 937 break; 938 case SDL_ANDROID_LIFECYCLE_LOWMEMORY: 939 // We don't need more than one low memory event queued 940 index = FindLifecycleEvent(SDL_ANDROID_LIFECYCLE_LOWMEMORY); 941 if (index >= 0) { 942 add_event = false; 943 } 944 break; 945 case SDL_ANDROID_LIFECYCLE_DESTROY: 946 // Remove all other events, we're done! 947 while (Android_NumLifecycleEvents > 0) { 948 RemoveLifecycleEvent(0); 949 } 950 break; 951 default: 952 SDL_assert(!"Sending unexpected lifecycle event"); 953 add_event = false; 954 break; 955 } 956 957 if (add_event) { 958 SDL_assert(Android_NumLifecycleEvents < SDL_arraysize(Android_LifecycleEvents)); 959 Android_LifecycleEvents[Android_NumLifecycleEvents++] = event; 960 SDL_SignalSemaphore(Android_LifecycleEventSem); 961 } 962 } 963 SDL_UnlockMutex(Android_LifecycleMutex); 964} 965 966bool Android_WaitLifecycleEvent(SDL_AndroidLifecycleEvent *event, Sint64 timeoutNS) 967{ 968 bool got_event = false; 969 970 while (!got_event && SDL_WaitSemaphoreTimeoutNS(Android_LifecycleEventSem, timeoutNS)) { 971 SDL_LockMutex(Android_LifecycleMutex); 972 { 973 if (Android_NumLifecycleEvents > 0) { 974 *event = Android_LifecycleEvents[0]; 975 RemoveLifecycleEvent(0); 976 got_event = true; 977 } 978 } 979 SDL_UnlockMutex(Android_LifecycleMutex); 980 } 981 return got_event; 982} 983 984void Android_LockActivityMutex(void) 985{ 986 SDL_LockMutex(Android_ActivityMutex); 987} 988 989void Android_UnlockActivityMutex(void) 990{ 991 SDL_UnlockMutex(Android_ActivityMutex); 992} 993 994// Drop file 995JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDropFile)( 996 JNIEnv *env, jclass jcls, 997 jstring filename) 998{ 999 const char *path = (*env)->GetStringUTFChars(env, filename, NULL); 1000 SDL_SendDropFile(NULL, NULL, path); 1001 (*env)->ReleaseStringUTFChars(env, filename, path); 1002 SDL_SendDropComplete(NULL); 1003} 1004 1005// Set screen resolution 1006JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetScreenResolution)( 1007 JNIEnv *env, jclass jcls, 1008 jint surfaceWidth, jint surfaceHeight, 1009 jint deviceWidth, jint deviceHeight, jfloat density, jfloat rate) 1010{ 1011 SDL_LockMutex(Android_ActivityMutex); 1012 1013 Android_SetScreenResolution(surfaceWidth, surfaceHeight, deviceWidth, deviceHeight, density, rate); 1014 1015 SDL_UnlockMutex(Android_ActivityMutex); 1016} 1017 1018// Resize 1019JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeResize)( 1020 JNIEnv *env, jclass jcls) 1021{ 1022 SDL_LockMutex(Android_ActivityMutex); 1023 1024 if (Android_Window) { 1025 Android_SendResize(Android_Window); 1026 } 1027 1028 SDL_UnlockMutex(Android_ActivityMutex); 1029} 1030 1031JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetNaturalOrientation)( 1032 JNIEnv *env, jclass jcls, 1033 jint orientation) 1034{ 1035 displayNaturalOrientation = (SDL_DisplayOrientation)orientation; 1036} 1037 1038JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeRotationChanged)( 1039 JNIEnv *env, jclass jcls, 1040 jint rotation) 1041{ 1042 SDL_LockMutex(Android_ActivityMutex); 1043 1044 if (displayNaturalOrientation == SDL_ORIENTATION_LANDSCAPE) { 1045 rotation += 90; 1046 } 1047 1048 switch (rotation % 360) { 1049 case 0: 1050 displayCurrentOrientation = SDL_ORIENTATION_PORTRAIT; 1051 break; 1052 case 90: 1053 displayCurrentOrientation = SDL_ORIENTATION_LANDSCAPE; 1054 break; 1055 case 180: 1056 displayCurrentOrientation = SDL_ORIENTATION_PORTRAIT_FLIPPED; 1057 break; 1058 case 270: 1059 displayCurrentOrientation = SDL_ORIENTATION_LANDSCAPE_FLIPPED; 1060 break; 1061 default: 1062 displayCurrentOrientation = SDL_ORIENTATION_UNKNOWN; 1063 break; 1064 } 1065 1066 Android_SetOrientation(displayCurrentOrientation); 1067 1068 SDL_UnlockMutex(Android_ActivityMutex); 1069} 1070 1071JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeInsetsChanged)( 1072 JNIEnv *env, jclass jcls, 1073 jint left, jint right, jint top, jint bottom) 1074{ 1075 SDL_LockMutex(Android_ActivityMutex); 1076 1077 Android_SetWindowSafeAreaInsets(left, right, top, bottom); 1078 1079 SDL_UnlockMutex(Android_ActivityMutex); 1080} 1081 1082JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeAddTouch)( 1083 JNIEnv *env, jclass cls, 1084 jint touchId, jstring name) 1085{ 1086 const char *utfname = (*env)->GetStringUTFChars(env, name, NULL); 1087 1088 SDL_AddTouch((SDL_TouchID)touchId, SDL_TOUCH_DEVICE_DIRECT, utfname); 1089 1090 (*env)->ReleaseStringUTFChars(env, name, utfname); 1091} 1092 1093JNIEXPORT void JNICALL 1094SDL_JAVA_AUDIO_INTERFACE(addAudioDevice)(JNIEnv *env, jclass jcls, jboolean recording, 1095 jstring name, jint device_id) 1096{ 1097#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES 1098 if (SDL_GetCurrentAudioDriver() != NULL) { 1099 void *handle = (void *)((size_t)device_id); 1100 if (!SDL_FindPhysicalAudioDeviceByHandle(handle)) { 1101 const char *utf8name = (*env)->GetStringUTFChars(env, name, NULL); 1102 SDL_AddAudioDevice(recording, SDL_strdup(utf8name), NULL, handle); 1103 (*env)->ReleaseStringUTFChars(env, name, utf8name); 1104 } 1105 } 1106#endif 1107} 1108 1109JNIEXPORT void JNICALL 1110SDL_JAVA_AUDIO_INTERFACE(removeAudioDevice)(JNIEnv *env, jclass jcls, jboolean recording, 1111 jint device_id) 1112{ 1113#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES 1114 if (SDL_GetCurrentAudioDriver() != NULL) { 1115 SDL_Log("Removing device with handle %d, recording %d", device_id, recording); 1116 SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByHandle((void *)((size_t)device_id))); 1117 } 1118#endif 1119} 1120 1121// Paddown 1122JNIEXPORT jboolean JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadDown)( 1123 JNIEnv *env, jclass jcls, 1124 jint device_id, jint keycode) 1125{ 1126#ifdef SDL_JOYSTICK_ANDROID 1127 return Android_OnPadDown(device_id, keycode); 1128#else 1129 return false; 1130#endif // SDL_JOYSTICK_ANDROID 1131} 1132 1133// Padup 1134JNIEXPORT jboolean JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadUp)( 1135 JNIEnv *env, jclass jcls, 1136 jint device_id, jint keycode) 1137{ 1138#ifdef SDL_JOYSTICK_ANDROID 1139 return Android_OnPadUp(device_id, keycode); 1140#else 1141 return false; 1142#endif // SDL_JOYSTICK_ANDROID 1143} 1144 1145// Joy 1146JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeJoy)( 1147 JNIEnv *env, jclass jcls, 1148 jint device_id, jint axis, jfloat value) 1149{ 1150#ifdef SDL_JOYSTICK_ANDROID 1151 Android_OnJoy(device_id, axis, value); 1152#endif // SDL_JOYSTICK_ANDROID 1153} 1154 1155// POV Hat 1156JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeHat)( 1157 JNIEnv *env, jclass jcls, 1158 jint device_id, jint hat_id, jint x, jint y) 1159{ 1160#ifdef SDL_JOYSTICK_ANDROID 1161 Android_OnHat(device_id, hat_id, x, y); 1162#endif // SDL_JOYSTICK_ANDROID 1163} 1164 1165JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick)( 1166 JNIEnv *env, jclass jcls, 1167 jint device_id, jstring device_name, jstring device_desc, 1168 jint vendor_id, jint product_id, 1169 jint button_mask, jint naxes, jint axis_mask, jint nhats, jboolean can_rumble) 1170{ 1171#ifdef SDL_JOYSTICK_ANDROID 1172 const char *name = (*env)->GetStringUTFChars(env, device_name, NULL); 1173 const char *desc = (*env)->GetStringUTFChars(env, device_desc, NULL); 1174 1175 Android_AddJoystick(device_id, name, desc, vendor_id, product_id, button_mask, naxes, axis_mask, nhats, can_rumble); 1176 1177 (*env)->ReleaseStringUTFChars(env, device_name, name); 1178 (*env)->ReleaseStringUTFChars(env, device_desc, desc); 1179#endif // SDL_JOYSTICK_ANDROID 1180} 1181 1182JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveJoystick)( 1183 JNIEnv *env, jclass jcls, 1184 jint device_id) 1185{ 1186#ifdef SDL_JOYSTICK_ANDROID 1187 Android_RemoveJoystick(device_id); 1188#endif // SDL_JOYSTICK_ANDROID 1189} 1190 1191JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddHaptic)( 1192 JNIEnv *env, jclass jcls, jint device_id, jstring device_name) 1193{ 1194#ifdef SDL_HAPTIC_ANDROID 1195 const char *name = (*env)->GetStringUTFChars(env, device_name, NULL); 1196 1197 Android_AddHaptic(device_id, name); 1198 1199 (*env)->ReleaseStringUTFChars(env, device_name, name); 1200#endif // SDL_HAPTIC_ANDROID 1201} 1202 1203JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveHaptic)( 1204 JNIEnv *env, jclass jcls, jint device_id) 1205{ 1206#ifdef SDL_HAPTIC_ANDROID 1207 Android_RemoveHaptic(device_id); 1208#endif 1209} 1210 1211// Called from surfaceCreated() 1212JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceCreated)(JNIEnv *env, jclass jcls) 1213{ 1214 SDL_LockMutex(Android_ActivityMutex); 1215 1216 if (Android_Window) { 1217 SDL_WindowData *data = Android_Window->internal; 1218 1219 data->native_window = Android_JNI_GetNativeWindow(); 1220 SDL_SetPointerProperty(SDL_GetWindowProperties(Android_Window), SDL_PROP_WINDOW_ANDROID_WINDOW_POINTER, data->native_window); 1221 if (data->native_window == NULL) { 1222 SDL_SetError("Could not fetch native window from UI thread"); 1223 } 1224 } 1225 1226 SDL_UnlockMutex(Android_ActivityMutex); 1227} 1228 1229// Called from surfaceChanged() 1230JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceChanged)(JNIEnv *env, jclass jcls) 1231{ 1232 SDL_LockMutex(Android_ActivityMutex); 1233 1234#ifdef SDL_VIDEO_OPENGL_EGL 1235 if (Android_Window && (Android_Window->flags & SDL_WINDOW_OPENGL)) { 1236 SDL_VideoDevice *_this = SDL_GetVideoDevice(); 1237 SDL_WindowData *data = Android_Window->internal; 1238 1239 // If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here 1240 if (data->egl_surface == EGL_NO_SURFACE) { 1241 data->egl_surface = SDL_EGL_CreateSurface(_this, Android_Window, (NativeWindowType)data->native_window); 1242 SDL_SetPointerProperty(SDL_GetWindowProperties(Android_Window), SDL_PROP_WINDOW_ANDROID_SURFACE_POINTER, data->egl_surface); 1243 } 1244 1245 // GL Context handling is done in the event loop because this function is run from the Java thread 1246 } 1247#endif 1248 1249 SDL_UnlockMutex(Android_ActivityMutex); 1250} 1251 1252// Called from surfaceDestroyed() 1253JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceDestroyed)(JNIEnv *env, jclass jcls) 1254{ 1255 int nb_attempt = 50; 1256 1257retry: 1258 1259 SDL_LockMutex(Android_ActivityMutex); 1260 1261 if (Android_Window) { 1262 SDL_WindowData *data = Android_Window->internal; 1263 1264 // Wait for Main thread being paused and context un-activated to release 'egl_surface' 1265 if ((Android_Window->flags & SDL_WINDOW_OPENGL) && !data->backup_done) { 1266 nb_attempt -= 1; 1267 if (nb_attempt == 0) { 1268 SDL_SetError("Try to release egl_surface with context probably still active"); 1269 } else { 1270 SDL_UnlockMutex(Android_ActivityMutex); 1271 SDL_Delay(10); 1272 goto retry; 1273 } 1274 } 1275 1276#ifdef SDL_VIDEO_OPENGL_EGL 1277 if (data->egl_surface != EGL_NO_SURFACE) { 1278 SDL_EGL_DestroySurface(SDL_GetVideoDevice(), data->egl_surface); 1279 data->egl_surface = EGL_NO_SURFACE; 1280 } 1281#endif 1282 1283 if (data->native_window) { 1284 ANativeWindow_release(data->native_window); 1285 data->native_window = NULL; 1286 } 1287 1288 // GL Context handling is done in the event loop because this function is run from the Java thread 1289 } 1290 1291 SDL_UnlockMutex(Android_ActivityMutex); 1292} 1293 1294// Keydown 1295JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyDown)( 1296 JNIEnv *env, jclass jcls, 1297 jint keycode) 1298{ 1299 SDL_LockMutex(Android_ActivityMutex); 1300 1301 if (Android_Window) { 1302 Android_OnKeyDown(keycode); 1303 } 1304 1305 SDL_UnlockMutex(Android_ActivityMutex); 1306} 1307 1308// Keyup 1309JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyUp)( 1310 JNIEnv *env, jclass jcls, 1311 jint keycode) 1312{ 1313 SDL_LockMutex(Android_ActivityMutex); 1314 1315 if (Android_Window) { 1316 Android_OnKeyUp(keycode); 1317 } 1318 1319 SDL_UnlockMutex(Android_ActivityMutex); 1320} 1321 1322// Virtual keyboard return key might stop text input 1323JNIEXPORT jboolean JNICALL SDL_JAVA_INTERFACE(onNativeSoftReturnKey)( 1324 JNIEnv *env, jclass jcls) 1325{ 1326 if (SDL_GetHintBoolean(SDL_HINT_RETURN_KEY_HIDES_IME, false)) { 1327 SDL_StopTextInput(Android_Window); 1328 return JNI_TRUE; 1329 } 1330 return JNI_FALSE; 1331} 1332 1333// Keyboard Focus Lost 1334JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyboardFocusLost)( 1335 JNIEnv *env, jclass jcls) 1336{ 1337 // Calling SDL_StopTextInput will take care of hiding the keyboard and cleaning up the DummyText widget 1338 SDL_StopTextInput(Android_Window); 1339} 1340 1341// Touch 1342JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeTouch)( 1343 JNIEnv *env, jclass jcls, 1344 jint touch_device_id_in, jint pointer_finger_id_in, 1345 jint action, jfloat x, jfloat y, jfloat p) 1346{ 1347 SDL_LockMutex(Android_ActivityMutex); 1348 1349 Android_OnTouch(Android_Window, touch_device_id_in, pointer_finger_id_in, action, x, y, p); 1350 1351 SDL_UnlockMutex(Android_ActivityMutex); 1352} 1353 1354// Mouse 1355JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeMouse)( 1356 JNIEnv *env, jclass jcls, 1357 jint button, jint action, jfloat x, jfloat y, jboolean relative) 1358{ 1359 SDL_LockMutex(Android_ActivityMutex); 1360 1361 Android_OnMouse(Android_Window, button, action, x, y, relative); 1362 1363 SDL_UnlockMutex(Android_ActivityMutex); 1364} 1365 1366// Pen 1367JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePen)( 1368 JNIEnv *env, jclass jcls, 1369 jint pen_id_in, jint button, jint action, jfloat x, jfloat y, jfloat p) 1370{ 1371 SDL_LockMutex(Android_ActivityMutex); 1372 1373 Android_OnPen(Android_Window, pen_id_in, button, action, x, y, p); 1374 1375 SDL_UnlockMutex(Android_ActivityMutex); 1376} 1377 1378// Accelerometer 1379JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeAccel)( 1380 JNIEnv *env, jclass jcls, 1381 jfloat x, jfloat y, jfloat z) 1382{ 1383 fLastAccelerometer[0] = x; 1384 fLastAccelerometer[1] = y; 1385 fLastAccelerometer[2] = z; 1386 bHasNewData = true; 1387} 1388 1389// Clipboard 1390JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeClipboardChanged)( 1391 JNIEnv *env, jclass jcls) 1392{ 1393 // TODO: compute new mime types 1394 SDL_SendClipboardUpdate(false, NULL, 0); 1395} 1396 1397// Low memory 1398JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeLowMemory)( 1399 JNIEnv *env, jclass cls) 1400{ 1401 Android_SendLifecycleEvent(SDL_ANDROID_LIFECYCLE_LOWMEMORY); 1402} 1403 1404/* Locale 1405 * requires android:configChanges="layoutDirection|locale" in AndroidManifest.xml */ 1406JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeLocaleChanged)( 1407 JNIEnv *env, jclass cls) 1408{ 1409 SDL_SendAppEvent(SDL_EVENT_LOCALE_CHANGED); 1410} 1411 1412// Dark mode 1413JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDarkModeChanged)( 1414 JNIEnv *env, jclass cls, jboolean enabled) 1415{ 1416 Android_SetDarkMode(enabled); 1417} 1418 1419// Send Quit event to "SDLThread" thread 1420JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSendQuit)( 1421 JNIEnv *env, jclass cls) 1422{ 1423 Android_SendLifecycleEvent(SDL_ANDROID_LIFECYCLE_DESTROY); 1424} 1425 1426// Activity ends 1427JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeQuit)( 1428 JNIEnv *env, jclass cls) 1429{ 1430 const char *str; 1431 1432 if (Android_ActivityMutex) { 1433 SDL_DestroyMutex(Android_ActivityMutex); 1434 Android_ActivityMutex = NULL; 1435 } 1436 1437 if (Android_LifecycleMutex) { 1438 SDL_DestroyMutex(Android_LifecycleMutex); 1439 Android_LifecycleMutex = NULL; 1440 } 1441 1442 if (Android_LifecycleEventSem) { 1443 SDL_DestroySemaphore(Android_LifecycleEventSem); 1444 Android_LifecycleEventSem = NULL; 1445 } 1446 1447 Android_NumLifecycleEvents = 0; 1448 1449 Internal_Android_Destroy_AssetManager(); 1450 1451 str = SDL_GetError(); 1452 if (str && str[0]) { 1453 __android_log_print(ANDROID_LOG_ERROR, "SDL", "SDLActivity thread ends (error=%s)", str); 1454 } else { 1455 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDLActivity thread ends"); 1456 } 1457} 1458 1459// Pause 1460JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativePause)( 1461 JNIEnv *env, jclass cls) 1462{ 1463 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativePause()"); 1464 1465 Android_SendLifecycleEvent(SDL_ANDROID_LIFECYCLE_PAUSE); 1466} 1467 1468// Resume 1469JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeResume)( 1470 JNIEnv *env, jclass cls) 1471{ 1472 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeResume()"); 1473 1474 Android_SendLifecycleEvent(SDL_ANDROID_LIFECYCLE_RESUME); 1475} 1476 1477JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeFocusChanged)( 1478 JNIEnv *env, jclass cls, jboolean hasFocus) 1479{ 1480 SDL_LockMutex(Android_ActivityMutex); 1481 1482 if (Android_Window) { 1483 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeFocusChanged()"); 1484 SDL_SendWindowEvent(Android_Window, (hasFocus ? SDL_EVENT_WINDOW_FOCUS_GAINED : SDL_EVENT_WINDOW_FOCUS_LOST), 0, 0); 1485 } 1486 1487 SDL_UnlockMutex(Android_ActivityMutex); 1488} 1489 1490JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeCommitText)( 1491 JNIEnv *env, jclass cls, 1492 jstring text, jint newCursorPosition) 1493{ 1494 const char *utftext = (*env)->GetStringUTFChars(env, text, NULL); 1495 1496 SDL_SendKeyboardText(utftext); 1497 1498 (*env)->ReleaseStringUTFChars(env, text, utftext); 1499} 1500 1501JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeGenerateScancodeForUnichar)( 1502 JNIEnv *env, jclass cls, 1503 jchar chUnicode) 1504{ 1505 SDL_SendKeyboardUnicodeKey(0, chUnicode); 1506} 1507 1508JNIEXPORT jstring JNICALL SDL_JAVA_INTERFACE(nativeGetHint)( 1509 JNIEnv *env, jclass cls, 1510 jstring name) 1511{ 1512 const char *utfname = (*env)->GetStringUTFChars(env, name, NULL); 1513 const char *hint = SDL_GetHint(utfname); 1514 1515 jstring result = (*env)->NewStringUTF(env, hint); 1516 (*env)->ReleaseStringUTFChars(env, name, utfname); 1517 1518 return result; 1519} 1520 1521JNIEXPORT jboolean JNICALL SDL_JAVA_INTERFACE(nativeGetHintBoolean)( 1522 JNIEnv *env, jclass cls, 1523 jstring name, jboolean default_value) 1524{ 1525 jboolean result; 1526 1527 const char *utfname = (*env)->GetStringUTFChars(env, name, NULL); 1528 result = SDL_GetHintBoolean(utfname, default_value); 1529 (*env)->ReleaseStringUTFChars(env, name, utfname); 1530 1531 return result; 1532} 1533 1534JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetenv)( 1535 JNIEnv *env, jclass cls, 1536 jstring name, jstring value) 1537{ 1538 const char *utfname = (*env)->GetStringUTFChars(env, name, NULL); 1539 const char *utfvalue = (*env)->GetStringUTFChars(env, value, NULL); 1540 1541 // This is only called at startup, to initialize the environment 1542 // Note that we call setenv() directly to avoid affecting SDL environments 1543 setenv(utfname, utfvalue, 1); // This should NOT be SDL_setenv() 1544 1545 (*env)->ReleaseStringUTFChars(env, name, utfname); 1546 (*env)->ReleaseStringUTFChars(env, value, utfvalue); 1547} 1548 1549/******************************************************************************* 1550 Functions called by SDL into Java 1551*******************************************************************************/ 1552 1553static SDL_AtomicInt s_active; 1554struct LocalReferenceHolder 1555{ 1556 JNIEnv *m_env; 1557 const char *m_func; 1558}; 1559 1560static struct LocalReferenceHolder LocalReferenceHolder_Setup(const char *func) 1561{ 1562 struct LocalReferenceHolder refholder; 1563 refholder.m_env = NULL; 1564 refholder.m_func = func; 1565#ifdef DEBUG_JNI 1566 SDL_Log("Entering function %s", func); 1567#endif 1568 return refholder; 1569} 1570 1571static bool LocalReferenceHolder_Init(struct LocalReferenceHolder *refholder, JNIEnv *env) 1572{ 1573 const int capacity = 16; 1574 if ((*env)->PushLocalFrame(env, capacity) < 0) { 1575 SDL_SetError("Failed to allocate enough JVM local references"); 1576 return false; 1577 } 1578 SDL_AtomicIncRef(&s_active); 1579 refholder->m_env = env; 1580 return true; 1581} 1582 1583static void LocalReferenceHolder_Cleanup(struct LocalReferenceHolder *refholder) 1584{ 1585#ifdef DEBUG_JNI 1586 SDL_Log("Leaving function %s", refholder->m_func); 1587#endif 1588 if (refholder->m_env) { 1589 JNIEnv *env = refholder->m_env; 1590 (*env)->PopLocalFrame(env, NULL); 1591 SDL_AtomicDecRef(&s_active); 1592 } 1593} 1594 1595ANativeWindow *Android_JNI_GetNativeWindow(void) 1596{ 1597 ANativeWindow *anw = NULL; 1598 jobject s; 1599 JNIEnv *env = Android_JNI_GetEnv(); 1600 1601 s = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetNativeSurface); 1602 if (s) { 1603 anw = ANativeWindow_fromSurface(env, s); 1604 (*env)->DeleteLocalRef(env, s); 1605 } 1606 1607 return anw; 1608} 1609 1610void Android_JNI_SetActivityTitle(const char *title) 1611{ 1612 JNIEnv *env = Android_JNI_GetEnv(); 1613 1614 jstring jtitle = (*env)->NewStringUTF(env, title); 1615 (*env)->CallStaticBooleanMethod(env, mActivityClass, midSetActivityTitle, jtitle); 1616 (*env)->DeleteLocalRef(env, jtitle); 1617} 1618 1619void Android_JNI_SetWindowStyle(bool fullscreen) 1620{ 1621 JNIEnv *env = Android_JNI_GetEnv(); 1622 (*env)->CallStaticVoidMethod(env, mActivityClass, midSetWindowStyle, fullscreen ? 1 : 0); 1623} 1624 1625void Android_JNI_SetOrientation(int w, int h, int resizable, const char *hint) 1626{ 1627 JNIEnv *env = Android_JNI_GetEnv(); 1628 1629 jstring jhint = (*env)->NewStringUTF(env, (hint ? hint : "")); 1630 (*env)->CallStaticVoidMethod(env, mActivityClass, midSetOrientation, w, h, (resizable ? 1 : 0), jhint); 1631 (*env)->DeleteLocalRef(env, jhint); 1632} 1633 1634SDL_DisplayOrientation Android_JNI_GetDisplayNaturalOrientation(void) 1635{ 1636 return displayNaturalOrientation; 1637} 1638 1639SDL_DisplayOrientation Android_JNI_GetDisplayCurrentOrientation(void) 1640{ 1641 return displayCurrentOrientation; 1642} 1643 1644void Android_JNI_MinizeWindow(void) 1645{ 1646 JNIEnv *env = Android_JNI_GetEnv(); 1647 (*env)->CallStaticVoidMethod(env, mActivityClass, midMinimizeWindow); 1648} 1649 1650bool Android_JNI_ShouldMinimizeOnFocusLoss(void) 1651{ 1652 JNIEnv *env = Android_JNI_GetEnv(); 1653 return (*env)->CallStaticBooleanMethod(env, mActivityClass, midShouldMinimizeOnFocusLoss); 1654} 1655 1656bool Android_JNI_GetAccelerometerValues(float values[3]) 1657{ 1658 bool result = false; 1659 1660 if (bHasNewData) { 1661 int i; 1662 for (i = 0; i < 3; ++i) { 1663 values[i] = fLastAccelerometer[i]; 1664 } 1665 bHasNewData = false; 1666 result = true; 1667 } 1668 1669 return result; 1670} 1671 1672/* 1673 * Audio support 1674 */ 1675void Android_StartAudioHotplug(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording) 1676{ 1677 JNIEnv *env = Android_JNI_GetEnv(); 1678 // this will fire the callback for each existing device right away (which will eventually SDL_AddAudioDevice), and again later when things change. 1679 (*env)->CallStaticVoidMethod(env, mAudioManagerClass, midRegisterAudioDeviceCallback); 1680 *default_playback = *default_recording = NULL; // !!! FIXME: how do you decide the default device id? 1681} 1682 1683void Android_StopAudioHotplug(void) 1684{ 1685 JNIEnv *env = Android_JNI_GetEnv(); 1686 (*env)->CallStaticVoidMethod(env, mAudioManagerClass, midUnregisterAudioDeviceCallback); 1687} 1688 1689static void Android_JNI_AudioSetThreadPriority(int recording, int device_id) 1690{ 1691 JNIEnv *env = Android_JNI_GetEnv(); 1692 (*env)->CallStaticVoidMethod(env, mAudioManagerClass, midAudioSetThreadPriority, recording, device_id); 1693} 1694 1695void Android_AudioThreadInit(SDL_AudioDevice *device) 1696{ 1697 Android_JNI_AudioSetThreadPriority((int) device->recording, (int)device->instance_id); 1698} 1699 1700// Test for an exception and call SDL_SetError with its detail if one occurs 1701// If the parameter silent is truthy then SDL_SetError() will not be called. 1702static bool Android_JNI_ExceptionOccurred(bool silent) 1703{ 1704 JNIEnv *env = Android_JNI_GetEnv(); 1705 jthrowable exception; 1706 1707 // Detect mismatch LocalReferenceHolder_Init/Cleanup 1708 SDL_assert(SDL_GetAtomicInt(&s_active) > 0); 1709 1710 exception = (*env)->ExceptionOccurred(env); 1711 if (exception != NULL) { 1712 jmethodID mid; 1713 1714 // Until this happens most JNI operations have undefined behaviour 1715 (*env)->ExceptionClear(env); 1716 1717 if (!silent) { 1718 jclass exceptionClass = (*env)->GetObjectClass(env, exception); 1719 jclass classClass = (*env)->FindClass(env, "java/lang/Class"); 1720 jstring exceptionName; 1721 const char *exceptionNameUTF8; 1722 jstring exceptionMessage; 1723 1724 mid = (*env)->GetMethodID(env, classClass, "getName", "()Ljava/lang/String;"); 1725 exceptionName = (jstring)(*env)->CallObjectMethod(env, exceptionClass, mid); 1726 exceptionNameUTF8 = (*env)->GetStringUTFChars(env, exceptionName, 0); 1727 1728 mid = (*env)->GetMethodID(env, exceptionClass, "getMessage", "()Ljava/lang/String;"); 1729 exceptionMessage = (jstring)(*env)->CallObjectMethod(env, exception, mid); 1730 1731 if (exceptionMessage != NULL) { 1732 const char *exceptionMessageUTF8 = (*env)->GetStringUTFChars(env, exceptionMessage, 0); 1733 SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8); 1734 (*env)->ReleaseStringUTFChars(env, exceptionMessage, exceptionMessageUTF8); 1735 } else { 1736 SDL_SetError("%s", exceptionNameUTF8); 1737 } 1738 1739 (*env)->ReleaseStringUTFChars(env, exceptionName, exceptionNameUTF8); 1740 } 1741 1742 return true; 1743 } 1744 1745 return false; 1746} 1747 1748static void Internal_Android_Create_AssetManager(void) 1749{ 1750 1751 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); 1752 JNIEnv *env = Android_JNI_GetEnv(); 1753 jmethodID mid; 1754 jobject context; 1755 jobject javaAssetManager; 1756 1757 if (!LocalReferenceHolder_Init(&refs, env)) { 1758 LocalReferenceHolder_Cleanup(&refs); 1759 return; 1760 } 1761 1762 // context = SDLActivity.getContext(); 1763 context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext); 1764 1765 // javaAssetManager = context.getAssets(); 1766 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context), 1767 "getAssets", "()Landroid/content/res/AssetManager;"); 1768 javaAssetManager = (*env)->CallObjectMethod(env, context, mid); 1769 1770 /** 1771 * Given a Dalvik AssetManager object, obtain the corresponding native AAssetManager 1772 * object. Note that the caller is responsible for obtaining and holding a VM reference 1773 * to the jobject to prevent its being garbage collected while the native object is 1774 * in use. 1775 */ 1776 javaAssetManagerRef = (*env)->NewGlobalRef(env, javaAssetManager); 1777 asset_manager = AAssetManager_fromJava(env, javaAssetManagerRef); 1778 1779 if (!asset_manager) { 1780 (*env)->DeleteGlobalRef(env, javaAssetManagerRef); 1781 Android_JNI_ExceptionOccurred(true); 1782 } 1783 1784 LocalReferenceHolder_Cleanup(&refs); 1785} 1786 1787static void Internal_Android_Destroy_AssetManager(void) 1788{ 1789 JNIEnv *env = Android_JNI_GetEnv(); 1790 1791 if (asset_manager) { 1792 (*env)->DeleteGlobalRef(env, javaAssetManagerRef); 1793 asset_manager = NULL; 1794 } 1795} 1796 1797bool Android_JNI_FileOpen(void **puserdata, const char *fileName, const char *mode) 1798{ 1799 SDL_assert(puserdata != NULL); 1800 1801 AAsset *asset = NULL; 1802 *puserdata = NULL; 1803 1804 if (!asset_manager) { 1805 Internal_Android_Create_AssetManager(); 1806 } 1807 1808 if (!asset_manager) { 1809 return SDL_SetError("Couldn't create asset manager"); 1810 } 1811 1812 asset = AAssetManager_open(asset_manager, fileName, AASSET_MODE_UNKNOWN); 1813 if (!asset) { 1814 return SDL_SetError("Couldn't open asset '%s'", fileName); 1815 } 1816 1817 *puserdata = (void *)asset; 1818 return true; 1819} 1820 1821size_t Android_JNI_FileRead(void *userdata, void *buffer, size_t size, SDL_IOStatus *status) 1822{ 1823 const int bytes = AAsset_read((AAsset *)userdata, buffer, size); 1824 if (bytes < 0) { 1825 SDL_SetError("AAsset_read() failed"); 1826 return 0; 1827 } 1828 return (size_t)bytes; 1829} 1830 1831size_t Android_JNI_FileWrite(void *userdata, const void *buffer, size_t size, SDL_IOStatus *status) 1832{ 1833 SDL_SetError("Cannot write to Android package filesystem"); 1834 return 0; 1835} 1836 1837Sint64 Android_JNI_FileSize(void *userdata) 1838{ 1839 return (Sint64) AAsset_getLength64((AAsset *)userdata); 1840} 1841 1842Sint64 Android_JNI_FileSeek(void *userdata, Sint64 offset, SDL_IOWhence whence) 1843{ 1844 return (Sint64) AAsset_seek64((AAsset *)userdata, offset, (int)whence); 1845} 1846 1847bool Android_JNI_FileClose(void *userdata) 1848{ 1849 AAsset_close((AAsset *)userdata); 1850 return true; 1851} 1852 1853bool Android_JNI_SetClipboardText(const char *text) 1854{ 1855 JNIEnv *env = Android_JNI_GetEnv(); 1856 jstring string = (*env)->NewStringUTF(env, text); 1857 (*env)->CallStaticVoidMethod(env, mActivityClass, midClipboardSetText, string); 1858 (*env)->DeleteLocalRef(env, string); 1859 return true; 1860} 1861 1862char *Android_JNI_GetClipboardText(void) 1863{ 1864 JNIEnv *env = Android_JNI_GetEnv(); 1865 char *text = NULL; 1866 jstring string; 1867 1868 string = (*env)->CallStaticObjectMethod(env, mActivityClass, midClipboardGetText); 1869 if (string) { 1870 const char *utf = (*env)->GetStringUTFChars(env, string, 0); 1871 if (utf) { 1872 text = SDL_strdup(utf); 1873 (*env)->ReleaseStringUTFChars(env, string, utf); 1874 } 1875 (*env)->DeleteLocalRef(env, string); 1876 } 1877 1878 return (!text) ? SDL_strdup("") : text; 1879} 1880 1881bool Android_JNI_HasClipboardText(void) 1882{ 1883 JNIEnv *env = Android_JNI_GetEnv(); 1884 return (*env)->CallStaticBooleanMethod(env, mActivityClass, midClipboardHasText); 1885} 1886 1887/* returns 0 on success or -1 on error (others undefined then) 1888 * returns truthy or falsy value in plugged, charged and battery 1889 * returns the value in seconds and percent or -1 if not available 1890 */ 1891int Android_JNI_GetPowerInfo(int *plugged, int *charged, int *battery, int *seconds, int *percent) 1892{ 1893 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); 1894 JNIEnv *env = Android_JNI_GetEnv(); 1895 jmethodID mid; 1896 jobject context; 1897 jstring action; 1898 jclass cls; 1899 jobject filter; 1900 jobject intent; 1901 jstring iname; 1902 jmethodID imid; 1903 jstring bname; 1904 jmethodID bmid; 1905 if (!LocalReferenceHolder_Init(&refs, env)) { 1906 LocalReferenceHolder_Cleanup(&refs); 1907 return -1; 1908 } 1909 1910 // context = SDLActivity.getContext(); 1911 context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext); 1912 1913 action = (*env)->NewStringUTF(env, "android.intent.action.BATTERY_CHANGED"); 1914 1915 cls = (*env)->FindClass(env, "android/content/IntentFilter"); 1916 1917 mid = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/lang/String;)V"); 1918 filter = (*env)->NewObject(env, cls, mid, action); 1919 1920 (*env)->DeleteLocalRef(env, action); 1921 1922 mid = (*env)->GetMethodID(env, mActivityClass, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;"); 1923 intent = (*env)->CallObjectMethod(env, context, mid, NULL, filter); 1924 1925 (*env)->DeleteLocalRef(env, filter); 1926 1927 cls = (*env)->GetObjectClass(env, intent); 1928 1929 imid = (*env)->GetMethodID(env, cls, "getIntExtra", "(Ljava/lang/String;I)I"); 1930 1931 // Watch out for C89 scoping rules because of the macro 1932#define GET_INT_EXTRA(var, key) \ 1933 int var; \ 1934 iname = (*env)->NewStringUTF(env, key); \ 1935 (var) = (*env)->CallIntMethod(env, intent, imid, iname, -1); \ 1936 (*env)->DeleteLocalRef(env, iname); 1937 1938 bmid = (*env)->GetMethodID(env, cls, "getBooleanExtra", "(Ljava/lang/String;Z)Z"); 1939 1940 // Watch out for C89 scoping rules because of the macro 1941#define GET_BOOL_EXTRA(var, key) \ 1942 int var; \ 1943 bname = (*env)->NewStringUTF(env, key); \ 1944 (var) = (*env)->CallBooleanMethod(env, intent, bmid, bname, JNI_FALSE); \ 1945 (*env)->DeleteLocalRef(env, bname); 1946 1947 if (plugged) { 1948 // Watch out for C89 scoping rules because of the macro 1949 GET_INT_EXTRA(plug, "plugged") // == BatteryManager.EXTRA_PLUGGED (API 5) 1950 if (plug == -1) { 1951 LocalReferenceHolder_Cleanup(&refs); 1952 return -1; 1953 } 1954 // 1 == BatteryManager.BATTERY_PLUGGED_AC 1955 // 2 == BatteryManager.BATTERY_PLUGGED_USB 1956 *plugged = (0 < plug) ? 1 : 0; 1957 } 1958 1959 if (charged) { 1960 // Watch out for C89 scoping rules because of the macro 1961 GET_INT_EXTRA(status, "status") // == BatteryManager.EXTRA_STATUS (API 5) 1962 if (status == -1) { 1963 LocalReferenceHolder_Cleanup(&refs); 1964 return -1; 1965 } 1966 // 5 == BatteryManager.BATTERY_STATUS_FULL 1967 *charged = (status == 5) ? 1 : 0; 1968 } 1969 1970 if (battery) { 1971 GET_BOOL_EXTRA(present, "present") // == BatteryManager.EXTRA_PRESENT (API 5) 1972 *battery = present ? 1 : 0; 1973 } 1974 1975 if (seconds) { 1976 *seconds = -1; // not possible 1977 } 1978 1979 if (percent) { 1980 int level; 1981 int scale; 1982 1983 // Watch out for C89 scoping rules because of the macro 1984 { 1985 GET_INT_EXTRA(level_temp, "level") // == BatteryManager.EXTRA_LEVEL (API 5) 1986 level = level_temp; 1987 } 1988 // Watch out for C89 scoping rules because of the macro 1989 { 1990 GET_INT_EXTRA(scale_temp, "scale") // == BatteryManager.EXTRA_SCALE (API 5) 1991 scale = scale_temp; 1992 } 1993 1994 if ((level == -1) || (scale == -1)) { 1995 LocalReferenceHolder_Cleanup(&refs); 1996 return -1; 1997 } 1998 *percent = level * 100 / scale; 1999 } 2000 2001 (*env)->DeleteLocalRef(env, intent); 2002 2003 LocalReferenceHolder_Cleanup(&refs); 2004 return 0; 2005} 2006 2007// Add all touch devices 2008void Android_JNI_InitTouch(void) 2009{ 2010 JNIEnv *env = Android_JNI_GetEnv(); 2011 (*env)->CallStaticVoidMethod(env, mActivityClass, midInitTouch); 2012} 2013 2014void Android_JNI_PollInputDevices(void) 2015{ 2016 JNIEnv *env = Android_JNI_GetEnv(); 2017 (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midPollInputDevices); 2018} 2019 2020void Android_JNI_PollHapticDevices(void) 2021{ 2022 JNIEnv *env = Android_JNI_GetEnv(); 2023 (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midPollHapticDevices); 2024} 2025 2026void Android_JNI_HapticRun(int device_id, float intensity, int length) 2027{ 2028 JNIEnv *env = Android_JNI_GetEnv(); 2029 (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midHapticRun, device_id, intensity, length); 2030} 2031 2032void Android_JNI_HapticRumble(int device_id, float low_frequency_intensity, float high_frequency_intensity, int length) 2033{ 2034 JNIEnv *env = Android_JNI_GetEnv(); 2035 (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midHapticRumble, device_id, low_frequency_intensity, high_frequency_intensity, length); 2036} 2037 2038void Android_JNI_HapticStop(int device_id) 2039{ 2040 JNIEnv *env = Android_JNI_GetEnv(); 2041 (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midHapticStop, device_id); 2042} 2043 2044// See SDLActivity.java for constants. 2045#define COMMAND_SET_KEEP_SCREEN_ON 5 2046 2047bool SDL_SendAndroidMessage(Uint32 command, int param) 2048{ 2049 if (command < 0x8000) { 2050 return SDL_InvalidParamError("command"); 2051 } 2052 return Android_JNI_SendMessage(command, param); 2053} 2054 2055// sends message to be handled on the UI event dispatch thread 2056bool Android_JNI_SendMessage(int command, int param) 2057{ 2058 JNIEnv *env = Android_JNI_GetEnv(); 2059 return (*env)->CallStaticBooleanMethod(env, mActivityClass, midSendMessage, command, param); 2060} 2061 2062bool Android_JNI_SuspendScreenSaver(bool suspend) 2063{ 2064 return Android_JNI_SendMessage(COMMAND_SET_KEEP_SCREEN_ON, (suspend == false) ? 0 : 1); 2065} 2066 2067void Android_JNI_ShowScreenKeyboard(int input_type, SDL_Rect *inputRect) 2068{ 2069 JNIEnv *env = Android_JNI_GetEnv(); 2070 (*env)->CallStaticBooleanMethod(env, mActivityClass, midShowTextInput, 2071 input_type, 2072 inputRect->x, 2073 inputRect->y, 2074 inputRect->w, 2075 inputRect->h); 2076} 2077 2078void Android_JNI_HideScreenKeyboard(void) 2079{ 2080 // has to match Activity constant 2081 const int COMMAND_TEXTEDIT_HIDE = 3; 2082 Android_JNI_SendMessage(COMMAND_TEXTEDIT_HIDE, 0); 2083} 2084 2085bool Android_JNI_IsScreenKeyboardShown(void) 2086{ 2087 JNIEnv *env = Android_JNI_GetEnv(); 2088 jboolean is_shown = 0; 2089 is_shown = (*env)->CallStaticBooleanMethod(env, mActivityClass, midIsScreenKeyboardShown); 2090 return is_shown; 2091} 2092 2093bool Android_JNI_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID) 2094{ 2095 JNIEnv *env; 2096 jclass clazz; 2097 jmethodID mid; 2098 jobject context; 2099 jstring title; 2100 jstring message; 2101 jintArray button_flags; 2102 jintArray button_ids; 2103 jobjectArray button_texts; 2104 jintArray colors; 2105 jobject text; 2106 jint temp; 2107 int i; 2108 2109 env = Android_JNI_GetEnv(); 2110 2111 // convert parameters 2112 2113 clazz = (*env)->FindClass(env, "java/lang/String"); 2114 2115 title = (*env)->NewStringUTF(env, messageboxdata->title); 2116 message = (*env)->NewStringUTF(env, messageboxdata->message); 2117 2118 button_flags = (*env)->NewIntArray(env, messageboxdata->numbuttons); 2119 button_ids = (*env)->NewIntArray(env, messageboxdata->numbuttons); 2120 button_texts = (*env)->NewObjectArray(env, messageboxdata->numbuttons, 2121 clazz, NULL); 2122 for (i = 0; i < messageboxdata->numbuttons; ++i) { 2123 const SDL_MessageBoxButtonData *sdlButton; 2124 2125 if (messageboxdata->flags & SDL_MESSAGEBOX_BUTTONS_RIGHT_TO_LEFT) { 2126 sdlButton = &messageboxdata->buttons[messageboxdata->numbuttons - 1 - i]; 2127 } else { 2128 sdlButton = &messageboxdata->buttons[i]; 2129 } 2130 2131 temp = sdlButton->flags; 2132 (*env)->SetIntArrayRegion(env, button_flags, i, 1, &temp); 2133 temp = sdlButton->buttonID; 2134 (*env)->SetIntArrayRegion(env, button_ids, i, 1, &temp); 2135 text = (*env)->NewStringUTF(env, sdlButton->text); 2136 (*env)->SetObjectArrayElement(env, button_texts, i, text); 2137 (*env)->DeleteLocalRef(env, text); 2138 } 2139 2140 if (messageboxdata->colorScheme) { 2141 colors = (*env)->NewIntArray(env, SDL_MESSAGEBOX_COLOR_COUNT); 2142 for (i = 0; i < SDL_MESSAGEBOX_COLOR_COUNT; ++i) { 2143 temp = (0xFFU << 24) | 2144 (messageboxdata->colorScheme->colors[i].r << 16) | 2145 (messageboxdata->colorScheme->colors[i].g << 8) | 2146 (messageboxdata->colorScheme->colors[i].b << 0); 2147 (*env)->SetIntArrayRegion(env, colors, i, 1, &temp); 2148 } 2149 } else { 2150 colors = NULL; 2151 } 2152 2153 (*env)->DeleteLocalRef(env, clazz); 2154 2155 // context = SDLActivity.getContext(); 2156 context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext); 2157 2158 clazz = (*env)->GetObjectClass(env, context); 2159 2160 mid = (*env)->GetMethodID(env, clazz, 2161 "messageboxShowMessageBox", "(ILjava/lang/String;Ljava/lang/String;[I[I[Ljava/lang/String;[I)I"); 2162 *buttonID = (*env)->CallIntMethod(env, context, mid, 2163 messageboxdata->flags, 2164 title, 2165 message, 2166 button_flags, 2167 button_ids, 2168 button_texts, 2169 colors); 2170 2171 (*env)->DeleteLocalRef(env, context); 2172 (*env)->DeleteLocalRef(env, clazz); 2173 2174 // delete parameters 2175 2176 (*env)->DeleteLocalRef(env, title); 2177 (*env)->DeleteLocalRef(env, message); 2178 (*env)->DeleteLocalRef(env, button_flags); 2179 (*env)->DeleteLocalRef(env, button_ids); 2180 (*env)->DeleteLocalRef(env, button_texts); 2181 (*env)->DeleteLocalRef(env, colors); 2182 2183 return true; 2184} 2185 2186/* 2187////////////////////////////////////////////////////////////////////////////// 2188// 2189// Functions exposed to SDL applications in SDL_system.h 2190////////////////////////////////////////////////////////////////////////////// 2191*/ 2192 2193void *SDL_GetAndroidJNIEnv(void) 2194{ 2195 return Android_JNI_GetEnv(); 2196} 2197 2198void *SDL_GetAndroidActivity(void) 2199{ 2200 // See SDL_system.h for caveats on using this function. 2201 2202 JNIEnv *env = Android_JNI_GetEnv(); 2203 if (!env) { 2204 return NULL; 2205 } 2206 2207 // return SDLActivity.getContext(); 2208 return (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext); 2209} 2210 2211int SDL_GetAndroidSDKVersion(void) 2212{ 2213 static int sdk_version; 2214 if (!sdk_version) { 2215 char sdk[PROP_VALUE_MAX] = { 0 }; 2216 if (__system_property_get("ro.build.version.sdk", sdk) != 0) { 2217 sdk_version = SDL_atoi(sdk); 2218 } 2219 } 2220 return sdk_version; 2221} 2222 2223bool SDL_IsAndroidTablet(void) 2224{ 2225 JNIEnv *env = Android_JNI_GetEnv(); 2226 return (*env)->CallStaticBooleanMethod(env, mActivityClass, midIsTablet); 2227} 2228 2229bool SDL_IsAndroidTV(void) 2230{ 2231 JNIEnv *env = Android_JNI_GetEnv(); 2232 return (*env)->CallStaticBooleanMethod(env, mActivityClass, midIsAndroidTV); 2233} 2234 2235bool SDL_IsChromebook(void) 2236{ 2237 JNIEnv *env = Android_JNI_GetEnv(); 2238 return (*env)->CallStaticBooleanMethod(env, mActivityClass, midIsChromebook); 2239} 2240 2241bool SDL_IsDeXMode(void) 2242{ 2243 JNIEnv *env = Android_JNI_GetEnv(); 2244 return (*env)->CallStaticBooleanMethod(env, mActivityClass, midIsDeXMode); 2245} 2246 2247void SDL_SendAndroidBackButton(void) 2248{ 2249 JNIEnv *env = Android_JNI_GetEnv(); 2250 (*env)->CallStaticVoidMethod(env, mActivityClass, midManualBackButton); 2251} 2252 2253const char *SDL_GetAndroidInternalStoragePath(void) 2254{ 2255 static char *s_AndroidInternalFilesPath = NULL; 2256 2257 if (!s_AndroidInternalFilesPath) { 2258 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); 2259 jmethodID mid; 2260 jobject context; 2261 jobject fileObject; 2262 jstring pathString; 2263 const char *path; 2264 2265 JNIEnv *env = Android_JNI_GetEnv(); 2266 if (!LocalReferenceHolder_Init(&refs, env)) { 2267 LocalReferenceHolder_Cleanup(&refs); 2268 return NULL; 2269 } 2270 2271 // context = SDLActivity.getContext(); 2272 context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext); 2273 if (!context) { 2274 SDL_SetError("Couldn't get Android context!"); 2275 LocalReferenceHolder_Cleanup(&refs); 2276 return NULL; 2277 } 2278 2279 // fileObj = context.getFilesDir(); 2280 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context), 2281 "getFilesDir", "()Ljava/io/File;"); 2282 fileObject = (*env)->CallObjectMethod(env, context, mid); 2283 if (!fileObject) { 2284 SDL_SetError("Couldn't get internal directory"); 2285 LocalReferenceHolder_Cleanup(&refs); 2286 return NULL; 2287 } 2288 2289 // path = fileObject.getCanonicalPath(); 2290 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject), 2291 "getCanonicalPath", "()Ljava/lang/String;"); 2292 pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid); 2293 if (Android_JNI_ExceptionOccurred(false)) { 2294 LocalReferenceHolder_Cleanup(&refs); 2295 return NULL; 2296 } 2297 2298 path = (*env)->GetStringUTFChars(env, pathString, NULL); 2299 s_AndroidInternalFilesPath = SDL_strdup(path); 2300 (*env)->ReleaseStringUTFChars(env, pathString, path); 2301 2302 LocalReferenceHolder_Cleanup(&refs); 2303 } 2304 return s_AndroidInternalFilesPath; 2305} 2306 2307Uint32 SDL_GetAndroidExternalStorageState(void) 2308{ 2309 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); 2310 jmethodID mid; 2311 jclass cls; 2312 jstring stateString; 2313 const char *state_string; 2314 Uint32 stateFlags; 2315 2316 JNIEnv *env = Android_JNI_GetEnv(); 2317 if (!LocalReferenceHolder_Init(&refs, env)) { 2318 LocalReferenceHolder_Cleanup(&refs); 2319 return 0; 2320 } 2321 2322 cls = (*env)->FindClass(env, "android/os/Environment"); 2323 mid = (*env)->GetStaticMethodID(env, cls, 2324 "getExternalStorageState", "()Ljava/lang/String;"); 2325 stateString = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid); 2326 2327 state_string = (*env)->GetStringUTFChars(env, stateString, NULL); 2328 2329 // Print an info message so people debugging know the storage state 2330 __android_log_print(ANDROID_LOG_INFO, "SDL", "external storage state: %s", state_string); 2331 2332 if (SDL_strcmp(state_string, "mounted") == 0) { 2333 stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ | 2334 SDL_ANDROID_EXTERNAL_STORAGE_WRITE; 2335 } else if (SDL_strcmp(state_string, "mounted_ro") == 0) { 2336 stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ; 2337 } else { 2338 stateFlags = 0; 2339 } 2340 (*env)->ReleaseStringUTFChars(env, stateString, state_string); 2341 2342 LocalReferenceHolder_Cleanup(&refs); 2343 2344 return stateFlags; 2345} 2346 2347const char *SDL_GetAndroidExternalStoragePath(void) 2348{ 2349 static char *s_AndroidExternalFilesPath = NULL; 2350 2351 if (!s_AndroidExternalFilesPath) { 2352 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); 2353 jmethodID mid; 2354 jobject context; 2355 jobject fileObject; 2356 jstring pathString; 2357 const char *path; 2358 2359 JNIEnv *env = Android_JNI_GetEnv(); 2360 if (!LocalReferenceHolder_Init(&refs, env)) { 2361 LocalReferenceHolder_Cleanup(&refs); 2362 return NULL; 2363 } 2364 2365 // context = SDLActivity.getContext(); 2366 context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext); 2367 2368 // fileObj = context.getExternalFilesDir(); 2369 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context), 2370 "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;"); 2371 fileObject = (*env)->CallObjectMethod(env, context, mid, NULL); 2372 if (!fileObject) { 2373 SDL_SetError("Couldn't get external directory"); 2374 LocalReferenceHolder_Cleanup(&refs); 2375 return NULL; 2376 } 2377 2378 // path = fileObject.getAbsolutePath(); 2379 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject), 2380 "getAbsolutePath", "()Ljava/lang/String;"); 2381 pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid); 2382 2383 path = (*env)->GetStringUTFChars(env, pathString, NULL); 2384 s_AndroidExternalFilesPath = SDL_strdup(path); 2385 (*env)->ReleaseStringUTFChars(env, pathString, path); 2386 2387 LocalReferenceHolder_Cleanup(&refs); 2388 } 2389 return s_AndroidExternalFilesPath; 2390} 2391 2392const char *SDL_GetAndroidCachePath(void) 2393{ 2394 // !!! FIXME: lots of duplication with SDL_GetAndroidExternalStoragePath and SDL_GetAndroidInternalStoragePath; consolidate these functions! 2395 static char *s_AndroidCachePath = NULL; 2396 2397 if (!s_AndroidCachePath) { 2398 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); 2399 jmethodID mid; 2400 jobject context; 2401 jobject fileObject; 2402 jstring pathString; 2403 const char *path; 2404 2405 JNIEnv *env = Android_JNI_GetEnv(); 2406 if (!LocalReferenceHolder_Init(&refs, env)) { 2407 LocalReferenceHolder_Cleanup(&refs); 2408 return NULL; 2409 } 2410 2411 // context = SDLActivity.getContext(); 2412 context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext); 2413 2414 // fileObj = context.getExternalFilesDir(); 2415 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context), 2416 "getCacheDir", "()Ljava/io/File;"); 2417 fileObject = (*env)->CallObjectMethod(env, context, mid, NULL); 2418 if (!fileObject) { 2419 SDL_SetError("Couldn't get cache directory"); 2420 LocalReferenceHolder_Cleanup(&refs); 2421 return NULL; 2422 } 2423 2424 // path = fileObject.getAbsolutePath(); 2425 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject), 2426 "getAbsolutePath", "()Ljava/lang/String;"); 2427 pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid); 2428 2429 path = (*env)->GetStringUTFChars(env, pathString, NULL); 2430 s_AndroidCachePath = SDL_strdup(path); 2431 (*env)->ReleaseStringUTFChars(env, pathString, path); 2432 2433 LocalReferenceHolder_Cleanup(&refs); 2434 } 2435 return s_AndroidCachePath; 2436} 2437 2438bool SDL_ShowAndroidToast(const char *message, int duration, int gravity, int xOffset, int yOffset) 2439{ 2440 return Android_JNI_ShowToast(message, duration, gravity, xOffset, yOffset); 2441} 2442 2443void Android_JNI_GetManifestEnvironmentVariables(void) 2444{ 2445 if (!mActivityClass || !midGetManifestEnvironmentVariables) { 2446 __android_log_print(ANDROID_LOG_WARN, "SDL", "Request to get environment variables before JNI is ready"); 2447 return; 2448 } 2449 2450 if (!bHasEnvironmentVariables) { 2451 JNIEnv *env = Android_JNI_GetEnv(); 2452 bool ret = (*env)->CallStaticBooleanMethod(env, mActivityClass, midGetManifestEnvironmentVariables); 2453 if (ret) { 2454 bHasEnvironmentVariables = true; 2455 } 2456 } 2457} 2458 2459int Android_JNI_CreateCustomCursor(SDL_Surface *surface, int hot_x, int hot_y) 2460{ 2461 JNIEnv *env = Android_JNI_GetEnv(); 2462 int custom_cursor = 0; 2463 jintArray pixels; 2464 pixels = (*env)->NewIntArray(env, surface->w * surface->h); 2465 if (pixels) { 2466 (*env)->SetIntArrayRegion(env, pixels, 0, surface->w * surface->h, (int *)surface->pixels); 2467 custom_cursor = (*env)->CallStaticIntMethod(env, mActivityClass, midCreateCustomCursor, pixels, surface->w, surface->h, hot_x, hot_y); 2468 (*env)->DeleteLocalRef(env, pixels); 2469 } else { 2470 SDL_OutOfMemory(); 2471 } 2472 return custom_cursor; 2473} 2474 2475void Android_JNI_DestroyCustomCursor(int cursorID) 2476{ 2477 JNIEnv *env = Android_JNI_GetEnv(); 2478 (*env)->CallStaticVoidMethod(env, mActivityClass, midDestroyCustomCursor, cursorID); 2479} 2480 2481bool Android_JNI_SetCustomCursor(int cursorID) 2482{ 2483 JNIEnv *env = Android_JNI_GetEnv(); 2484 return (*env)->CallStaticBooleanMethod(env, mActivityClass, midSetCustomCursor, cursorID); 2485} 2486 2487bool Android_JNI_SetSystemCursor(int cursorID) 2488{ 2489 JNIEnv *env = Android_JNI_GetEnv(); 2490 return (*env)->CallStaticBooleanMethod(env, mActivityClass, midSetSystemCursor, cursorID); 2491} 2492 2493bool Android_JNI_SupportsRelativeMouse(void) 2494{ 2495 JNIEnv *env = Android_JNI_GetEnv(); 2496 return (*env)->CallStaticBooleanMethod(env, mActivityClass, midSupportsRelativeMouse); 2497} 2498 2499bool Android_JNI_SetRelativeMouseEnabled(bool enabled) 2500{ 2501 JNIEnv *env = Android_JNI_GetEnv(); 2502 return (*env)->CallStaticBooleanMethod(env, mActivityClass, midSetRelativeMouseEnabled, (enabled == 1)); 2503} 2504 2505typedef struct NativePermissionRequestInfo 2506{ 2507 int request_code; 2508 char *permission; 2509 SDL_RequestAndroidPermissionCallback callback; 2510 void *userdata; 2511 struct NativePermissionRequestInfo *next; 2512} NativePermissionRequestInfo; 2513 2514static NativePermissionRequestInfo pending_permissions; 2515 2516JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativePermissionResult)( 2517 JNIEnv *env, jclass cls, 2518 jint requestCode, jboolean result) 2519{ 2520 SDL_LockMutex(Android_ActivityMutex); 2521 NativePermissionRequestInfo *prev = &pending_permissions; 2522 for (NativePermissionRequestInfo *info = prev->next; info != NULL; info = info->next) { 2523 if (info->request_code == (int) requestCode) { 2524 prev->next = info->next; 2525 SDL_UnlockMutex(Android_ActivityMutex); 2526 info->callback(info->userdata, info->permission, result ? true : false); 2527 SDL_free(info->permission); 2528 SDL_free(info); 2529 return; 2530 } 2531 prev = info; 2532 } 2533 2534 SDL_UnlockMutex(Android_ActivityMutex); 2535} 2536 2537bool SDL_RequestAndroidPermission(const char *permission, SDL_RequestAndroidPermissionCallback cb, void *userdata) 2538{ 2539 if (!permission) { 2540 return SDL_InvalidParamError("permission"); 2541 } else if (!cb) { 2542 return SDL_InvalidParamError("cb"); 2543 } 2544 2545 NativePermissionRequestInfo *info = (NativePermissionRequestInfo *) SDL_calloc(1, sizeof (NativePermissionRequestInfo)); 2546 if (!info) { 2547 return false; 2548 } 2549 2550 info->permission = SDL_strdup(permission); 2551 if (!info->permission) { 2552 SDL_free(info); 2553 return false; 2554 } 2555 2556 static SDL_AtomicInt next_request_code; 2557 info->request_code = SDL_AddAtomicInt(&next_request_code, 1); 2558 2559 info->callback = cb; 2560 info->userdata = userdata; 2561 2562 SDL_LockMutex(Android_ActivityMutex); 2563 info->next = pending_permissions.next; 2564 pending_permissions.next = info; 2565 SDL_UnlockMutex(Android_ActivityMutex); 2566 2567 JNIEnv *env = Android_JNI_GetEnv(); 2568 jstring jpermission = (*env)->NewStringUTF(env, permission); 2569 (*env)->CallStaticVoidMethod(env, mActivityClass, midRequestPermission, jpermission, info->request_code); 2570 (*env)->DeleteLocalRef(env, jpermission); 2571 2572 return true; 2573} 2574 2575// Show toast notification 2576bool Android_JNI_ShowToast(const char *message, int duration, int gravity, int xOffset, int yOffset) 2577{ 2578 bool result; 2579 JNIEnv *env = Android_JNI_GetEnv(); 2580 jstring jmessage = (*env)->NewStringUTF(env, message); 2581 result = (*env)->CallStaticBooleanMethod(env, mActivityClass, midShowToast, jmessage, duration, gravity, xOffset, yOffset); 2582 (*env)->DeleteLocalRef(env, jmessage); 2583 return result; 2584} 2585 2586bool Android_JNI_GetLocale(char *buf, size_t buflen) 2587{ 2588 AConfiguration *cfg; 2589 2590 SDL_assert(buflen > 6); 2591 2592 // Need to re-create the asset manager if locale has changed (SDL_EVENT_LOCALE_CHANGED) 2593 Internal_Android_Destroy_AssetManager(); 2594 2595 if (!asset_manager) { 2596 Internal_Android_Create_AssetManager(); 2597 } 2598 2599 if (!asset_manager) { 2600 return false; 2601 } 2602 2603 cfg = AConfiguration_new(); 2604 if (!cfg) { 2605 return false; 2606 } 2607 2608 { 2609 char language[2] = {}; 2610 char country[2] = {}; 2611 size_t id = 0; 2612 2613 AConfiguration_fromAssetManager(cfg, asset_manager); 2614 AConfiguration_getLanguage(cfg, language); 2615 AConfiguration_getCountry(cfg, country); 2616 2617 // Indonesian is "id" according to ISO 639.2, but on Android is "in" because of Java backwards compatibility 2618 if (language[0] == 'i' && language[1] == 'n') { 2619 language[1] = 'd'; 2620 } 2621 2622 // copy language (not null terminated) 2623 if (language[0]) { 2624 buf[id++] = language[0]; 2625 if (language[1]) { 2626 buf[id++] = language[1]; 2627 } 2628 } 2629 2630 buf[id++] = '_'; 2631 2632 // copy country (not null terminated) 2633 if (country[0]) { 2634 buf[id++] = country[0]; 2635 if (country[1]) { 2636 buf[id++] = country[1]; 2637 } 2638 } 2639 2640 buf[id++] = '\0'; 2641 SDL_assert(id <= buflen); 2642 } 2643 2644 AConfiguration_delete(cfg); 2645 2646 return true; 2647} 2648 2649bool Android_JNI_OpenURL(const char *url) 2650{ 2651 bool result; 2652 JNIEnv *env = Android_JNI_GetEnv(); 2653 jstring jurl = (*env)->NewStringUTF(env, url); 2654 result = (*env)->CallStaticBooleanMethod(env, mActivityClass, midOpenURL, jurl); 2655 (*env)->DeleteLocalRef(env, jurl); 2656 return result; 2657} 2658 2659int Android_JNI_OpenFileDescriptor(const char *uri, const char *mode) 2660{ 2661 // Get fopen-style modes 2662 int moderead = 0, modewrite = 0, modeappend = 0, modeupdate = 0; 2663 2664 for (const char *cmode = mode; *cmode; cmode++) { 2665 switch (*cmode) { 2666 case 'a': 2667 modeappend = 1; 2668 break; 2669 case 'r': 2670 moderead = 1; 2671 break; 2672 case 'w': 2673 modewrite = 1; 2674 break; 2675 case '+': 2676 modeupdate = 1; 2677 break; 2678 default: 2679 break; 2680 } 2681 } 2682 2683 // Translate fopen-style modes to ContentResolver modes. 2684 // Android only allows "r", "w", "wt", "wa", "rw" or "rwt". 2685 const char *contentResolverMode = "r"; 2686 2687 if (moderead) { 2688 if (modewrite) { 2689 contentResolverMode = "rwt"; 2690 } else { 2691 contentResolverMode = modeupdate ? "rw" : "r"; 2692 } 2693 } else if (modewrite) { 2694 contentResolverMode = modeupdate ? "rwt" : "wt"; 2695 } else if (modeappend) { 2696 contentResolverMode = modeupdate ? "rw" : "wa"; 2697 } 2698 2699 JNIEnv *env = Android_JNI_GetEnv(); 2700 jstring jstringUri = (*env)->NewStringUTF(env, uri); 2701 jstring jstringMode = (*env)->NewStringUTF(env, contentResolverMode); 2702 jint fd = (*env)->CallStaticIntMethod(env, mActivityClass, midOpenFileDescriptor, jstringUri, jstringMode); 2703 (*env)->DeleteLocalRef(env, jstringUri); 2704 (*env)->DeleteLocalRef(env, jstringMode); 2705 2706 if (fd == -1) { 2707 SDL_SetError("Unspecified error in JNI"); 2708 } 2709 2710 return fd; 2711} 2712 2713static struct AndroidFileDialog 2714{ 2715 int request_code; 2716 SDL_DialogFileCallback callback; 2717 void *userdata; 2718} mAndroidFileDialogData; 2719 2720JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeFileDialog)( 2721 JNIEnv *env, jclass jcls, 2722 jint requestCode, jobjectArray fileList, jint filter) 2723{ 2724 if (mAndroidFileDialogData.callback != NULL && mAndroidFileDialogData.request_code == requestCode) { 2725 if (fileList == NULL) { 2726 SDL_SetError("Unspecified error in JNI"); 2727 mAndroidFileDialogData.callback(mAndroidFileDialogData.userdata, NULL, -1); 2728 mAndroidFileDialogData.callback = NULL; 2729 return; 2730 } 2731 2732 // Convert fileList to string 2733 size_t count = (*env)->GetArrayLength(env, fileList); 2734 char **charFileList = SDL_calloc(count + 1, sizeof(char*)); 2735 2736 if (charFileList == NULL) { 2737 mAndroidFileDialogData.callback(mAndroidFileDialogData.userdata, NULL, -1); 2738 mAndroidFileDialogData.callback = NULL; 2739 return; 2740 } 2741 2742 // Convert to UTF-8 2743 // TODO: Fix modified UTF-8 to classic UTF-8 2744 for (int i = 0; i < count; i++) { 2745 jstring string = (*env)->GetObjectArrayElement(env, fileList, i); 2746 if (!string) { 2747 continue; 2748 } 2749 2750 const char *utf8string = (*env)->GetStringUTFChars(env, string, NULL); 2751 if (!utf8string) { 2752 (*env)->DeleteLocalRef(env, string); 2753 continue; 2754 } 2755 2756 char *newFile = SDL_strdup(utf8string); 2757 if (!newFile) { 2758 (*env)->ReleaseStringUTFChars(env, string, utf8string); 2759 (*env)->DeleteLocalRef(env, string); 2760 mAndroidFileDialogData.callback(mAndroidFileDialogData.userdata, NULL, -1); 2761 mAndroidFileDialogData.callback = NULL; 2762 2763 // Cleanup memory 2764 for (int j = 0; j < i; j++) { 2765 SDL_free(charFileList[j]); 2766 } 2767 SDL_free(charFileList); 2768 return; 2769 } 2770 2771 charFileList[i] = newFile; 2772 (*env)->ReleaseStringUTFChars(env, string, utf8string); 2773 (*env)->DeleteLocalRef(env, string); 2774 } 2775 2776 // Call user-provided callback 2777 SDL_ClearError(); 2778 mAndroidFileDialogData.callback(mAndroidFileDialogData.userdata, (const char *const *) charFileList, filter); 2779 mAndroidFileDialogData.callback = NULL; 2780 2781 // Cleanup memory 2782 for (int i = 0; i < count; i++) { 2783 SDL_free(charFileList[i]); 2784 } 2785 SDL_free(charFileList); 2786 } 2787} 2788 2789bool Android_JNI_OpenFileDialog( 2790 SDL_DialogFileCallback callback, void* userdata, 2791 const SDL_DialogFileFilter *filters, int nfilters, bool forwrite, 2792 bool multiple) 2793{ 2794 if (mAndroidFileDialogData.callback != NULL) { 2795 SDL_SetError("Only one file dialog can be run at a time."); 2796 return false; 2797 } 2798 2799 if (forwrite) { 2800 multiple = false; 2801 } 2802 2803 JNIEnv *env = Android_JNI_GetEnv(); 2804 2805 // Setup filters 2806 jobjectArray filtersArray = NULL; 2807 if (filters) { 2808 jclass stringClass = (*env)->FindClass(env, "java/lang/String"); 2809 filtersArray = (*env)->NewObjectArray(env, nfilters, stringClass, NULL); 2810 2811 // Convert to string 2812 for (int i = 0; i < nfilters; i++) { 2813 jstring str = (*env)->NewStringUTF(env, filters[i].pattern); 2814 (*env)->SetObjectArrayElement(env, filtersArray, i, str); 2815 (*env)->DeleteLocalRef(env, str); 2816 } 2817 } 2818 2819 // Setup data 2820 static SDL_AtomicInt next_request_code; 2821 mAndroidFileDialogData.request_code = SDL_AddAtomicInt(&next_request_code, 1); 2822 mAndroidFileDialogData.userdata = userdata; 2823 mAndroidFileDialogData.callback = callback; 2824 2825 // Invoke JNI 2826 jboolean success = (*env)->CallStaticBooleanMethod(env, mActivityClass, 2827 midShowFileDialog, filtersArray, (jboolean) multiple, (jboolean) forwrite, mAndroidFileDialogData.request_code); 2828 (*env)->DeleteLocalRef(env, filtersArray); 2829 if (!success) { 2830 mAndroidFileDialogData.callback = NULL; 2831 SDL_AddAtomicInt(&next_request_code, -1); 2832 SDL_SetError("Unspecified error in JNI"); 2833 2834 return false; 2835 } 2836 2837 return true; 2838} 2839 2840#endif // SDL_PLATFORM_ANDROID