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