Atlas - SDL_android.c

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