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