Atlas - SDL_android.c

Home / ext / SDL2 / src / core / android Lines: 1 | Size: 84041 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2018 Sam Lantinga <[email protected]> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20*/ 21#include "../../SDL_internal.h" 22#include "SDL_stdinc.h" 23#include "SDL_assert.h" 24#include "SDL_hints.h" 25#include "SDL_log.h" 26#include "SDL_main.h" 27 28#ifdef __ANDROID__ 29 30#include "SDL_system.h" 31#include "SDL_android.h" 32 33#include "keyinfotable.h" 34 35#include "../../events/SDL_events_c.h" 36#include "../../video/android/SDL_androidkeyboard.h" 37#include "../../video/android/SDL_androidmouse.h" 38#include "../../video/android/SDL_androidtouch.h" 39#include "../../video/android/SDL_androidvideo.h" 40#include "../../video/android/SDL_androidwindow.h" 41#include "../../joystick/android/SDL_sysjoystick_c.h" 42#include "../../haptic/android/SDL_syshaptic_c.h" 43 44#include <android/log.h> 45#include <pthread.h> 46#include <sys/types.h> 47#include <unistd.h> 48#include <dlfcn.h> 49/* #define LOG_TAG "SDL_android" */ 50/* #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) */ 51/* #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) */ 52#define LOGI(...) do {} while (0) 53#define LOGE(...) do {} while (0) 54 55 56#define SDL_JAVA_PREFIX org_libsdl_app 57#define CONCAT1(prefix, class, function) CONCAT2(prefix, class, function) 58#define CONCAT2(prefix, class, function) Java_ ## prefix ## _ ## class ## _ ## function 59#define SDL_JAVA_INTERFACE(function) CONCAT1(SDL_JAVA_PREFIX, SDLActivity, function) 60#define SDL_JAVA_AUDIO_INTERFACE(function) CONCAT1(SDL_JAVA_PREFIX, SDLAudioManager, function) 61#define SDL_JAVA_CONTROLLER_INTERFACE(function) CONCAT1(SDL_JAVA_PREFIX, SDLControllerManager, function) 62#define SDL_JAVA_INTERFACE_INPUT_CONNECTION(function) CONCAT1(SDL_JAVA_PREFIX, SDLInputConnection, function) 63 64 65/* Java class SDLActivity */ 66JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)( 67 JNIEnv* mEnv, jclass cls); 68 69JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeRunMain)( 70 JNIEnv* env, jclass cls, 71 jstring library, jstring function, jobject array); 72 73JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDropFile)( 74 JNIEnv* env, jclass jcls, 75 jstring filename); 76 77JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeResize)( 78 JNIEnv* env, jclass jcls, 79 jint surfaceWidth, jint surfaceHeight, 80 jint deviceWidth, jint deviceHeight, jint format, jfloat rate); 81 82JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceChanged)( 83 JNIEnv* env, jclass jcls); 84 85JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceDestroyed)( 86 JNIEnv* env, jclass jcls); 87 88JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyDown)( 89 JNIEnv* env, jclass jcls, 90 jint keycode); 91 92JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyUp)( 93 JNIEnv* env, jclass jcls, 94 jint keycode); 95 96JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyboardFocusLost)( 97 JNIEnv* env, jclass jcls); 98 99JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeTouch)( 100 JNIEnv* env, jclass jcls, 101 jint touch_device_id_in, jint pointer_finger_id_in, 102 jint action, jfloat x, jfloat y, jfloat p); 103 104JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeMouse)( 105 JNIEnv* env, jclass jcls, 106 jint button, jint action, jfloat x, jfloat y, jboolean relative); 107 108JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeAccel)( 109 JNIEnv* env, jclass jcls, 110 jfloat x, jfloat y, jfloat z); 111 112JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeClipboardChanged)( 113 JNIEnv* env, jclass jcls); 114 115JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeLowMemory)( 116 JNIEnv* env, jclass cls); 117 118JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeQuit)( 119 JNIEnv* env, jclass cls); 120 121JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativePause)( 122 JNIEnv* env, jclass cls); 123 124JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeResume)( 125 JNIEnv* env, jclass cls); 126 127JNIEXPORT jstring JNICALL SDL_JAVA_INTERFACE(nativeGetHint)( 128 JNIEnv* env, jclass cls, 129 jstring name); 130 131JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetenv)( 132 JNIEnv* env, jclass cls, 133 jstring name, jstring value); 134 135JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeEnvironmentVariablesSet)( 136 JNIEnv* env, jclass cls); 137 138JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeOrientationChanged)( 139 JNIEnv* env, jclass cls, 140 jint orientation); 141 142/* Java class SDLInputConnection */ 143JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeCommitText)( 144 JNIEnv* env, jclass cls, 145 jstring text, jint newCursorPosition); 146 147JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeSetComposingText)( 148 JNIEnv* env, jclass cls, 149 jstring text, jint newCursorPosition); 150 151/* Java class SDLAudioManager */ 152JNIEXPORT void JNICALL SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI)( 153 JNIEnv *env, jclass jcls); 154 155/* Java class SDLControllerManager */ 156JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI)( 157 JNIEnv *env, jclass jcls); 158 159JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadDown)( 160 JNIEnv* env, jclass jcls, 161 jint device_id, jint keycode); 162 163JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadUp)( 164 JNIEnv* env, jclass jcls, 165 jint device_id, jint keycode); 166 167JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeJoy)( 168 JNIEnv* env, jclass jcls, 169 jint device_id, jint axis, jfloat value); 170 171JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeHat)( 172 JNIEnv* env, jclass jcls, 173 jint device_id, jint hat_id, jint x, jint y); 174 175JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick)( 176 JNIEnv* env, jclass jcls, 177 jint device_id, jstring device_name, jstring device_desc, jint vendor_id, jint product_id, 178 jboolean is_accelerometer, jint button_mask, jint naxes, jint nhats, jint nballs); 179 180JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveJoystick)( 181 JNIEnv* env, jclass jcls, 182 jint device_id); 183 184JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddHaptic)( 185 JNIEnv* env, jclass jcls, 186 jint device_id, jstring device_name); 187 188JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveHaptic)( 189 JNIEnv* env, jclass jcls, 190 jint device_id); 191 192 193 194/* Uncomment this to log messages entering and exiting methods in this file */ 195/* #define DEBUG_JNI */ 196 197static void Android_JNI_ThreadDestroyed(void*); 198 199/******************************************************************************* 200 This file links the Java side of Android with libsdl 201*******************************************************************************/ 202#include <jni.h> 203 204 205/******************************************************************************* 206 Globals 207*******************************************************************************/ 208static pthread_key_t mThreadKey; 209static JavaVM* mJavaVM; 210 211/* Main activity */ 212static jclass mActivityClass; 213 214/* method signatures */ 215static jmethodID midGetNativeSurface; 216static jmethodID midSetActivityTitle; 217static jmethodID midSetWindowStyle; 218static jmethodID midSetOrientation; 219static jmethodID midGetContext; 220static jmethodID midIsTablet; 221static jmethodID midIsAndroidTV; 222static jmethodID midIsChromebook; 223static jmethodID midIsDeXMode; 224static jmethodID midManualBackButton; 225static jmethodID midInputGetInputDeviceIds; 226static jmethodID midSendMessage; 227static jmethodID midShowTextInput; 228static jmethodID midIsScreenKeyboardShown; 229static jmethodID midClipboardSetText; 230static jmethodID midClipboardGetText; 231static jmethodID midClipboardHasText; 232static jmethodID midOpenAPKExpansionInputStream; 233static jmethodID midGetManifestEnvironmentVariables; 234static jmethodID midGetDisplayDPI; 235static jmethodID midCreateCustomCursor; 236static jmethodID midSetCustomCursor; 237static jmethodID midSetSystemCursor; 238static jmethodID midSupportsRelativeMouse; 239static jmethodID midSetRelativeMouseEnabled; 240 241/* audio manager */ 242static jclass mAudioManagerClass; 243 244/* method signatures */ 245static jmethodID midAudioOpen; 246static jmethodID midAudioWriteShortBuffer; 247static jmethodID midAudioWriteByteBuffer; 248static jmethodID midAudioClose; 249static jmethodID midCaptureOpen; 250static jmethodID midCaptureReadShortBuffer; 251static jmethodID midCaptureReadByteBuffer; 252static jmethodID midCaptureClose; 253 254/* controller manager */ 255static jclass mControllerManagerClass; 256 257/* method signatures */ 258static jmethodID midPollInputDevices; 259static jmethodID midPollHapticDevices; 260static jmethodID midHapticRun; 261static jmethodID midHapticStop; 262 263/* static fields */ 264static jfieldID fidSeparateMouseAndTouch; 265 266/* Accelerometer data storage */ 267static float fLastAccelerometer[3]; 268static SDL_bool bHasNewData; 269 270static SDL_bool bHasEnvironmentVariables = SDL_FALSE; 271 272/******************************************************************************* 273 Functions called by JNI 274*******************************************************************************/ 275 276/* Library init */ 277JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) 278{ 279 JNIEnv *env; 280 mJavaVM = vm; 281 LOGI("JNI_OnLoad called"); 282 if ((*mJavaVM)->GetEnv(mJavaVM, (void**) &env, JNI_VERSION_1_4) != JNI_OK) { 283 LOGE("Failed to get the environment using GetEnv()"); 284 return -1; 285 } 286 /* 287 * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread 288 * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this 289 */ 290 if (pthread_key_create(&mThreadKey, Android_JNI_ThreadDestroyed) != 0) { 291 __android_log_print(ANDROID_LOG_ERROR, "SDL", "Error initializing pthread key"); 292 } 293 Android_JNI_SetupThread(); 294 295 return JNI_VERSION_1_4; 296} 297 298void checkJNIReady() 299{ 300 if (!mActivityClass || !mAudioManagerClass || !mControllerManagerClass) { 301 // We aren't fully initialized, let's just return. 302 return; 303 } 304 305 SDL_SetMainReady(); 306} 307 308/* Activity initialization -- called before SDL_main() to initialize JNI bindings */ 309JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv* mEnv, jclass cls) 310{ 311 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeSetupJNI()"); 312 313 Android_JNI_SetupThread(); 314 315 mActivityClass = (jclass)((*mEnv)->NewGlobalRef(mEnv, cls)); 316 317 midGetNativeSurface = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, 318 "getNativeSurface","()Landroid/view/Surface;"); 319 midSetActivityTitle = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, 320 "setActivityTitle","(Ljava/lang/String;)Z"); 321 midSetWindowStyle = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, 322 "setWindowStyle","(Z)V"); 323 midSetOrientation = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, 324 "setOrientation","(IIZLjava/lang/String;)V"); 325 midGetContext = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, 326 "getContext","()Landroid/content/Context;"); 327 midIsTablet = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, 328 "isTablet", "()Z"); 329 midIsAndroidTV = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, 330 "isAndroidTV","()Z"); 331 midIsChromebook = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, 332 "isChromebook", "()Z"); 333 midIsDeXMode = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, 334 "isDeXMode", "()Z"); 335 midManualBackButton = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, 336 "manualBackButton", "()V"); 337 midInputGetInputDeviceIds = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, 338 "inputGetInputDeviceIds", "(I)[I"); 339 midSendMessage = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, 340 "sendMessage", "(II)Z"); 341 midShowTextInput = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, 342 "showTextInput", "(IIII)Z"); 343 midIsScreenKeyboardShown = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, 344 "isScreenKeyboardShown","()Z"); 345 midClipboardSetText = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, 346 "clipboardSetText", "(Ljava/lang/String;)V"); 347 midClipboardGetText = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, 348 "clipboardGetText", "()Ljava/lang/String;"); 349 midClipboardHasText = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, 350 "clipboardHasText", "()Z"); 351 midOpenAPKExpansionInputStream = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, 352 "openAPKExpansionInputStream", "(Ljava/lang/String;)Ljava/io/InputStream;"); 353 354 midGetManifestEnvironmentVariables = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, 355 "getManifestEnvironmentVariables", "()Z"); 356 357 midGetDisplayDPI = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, "getDisplayDPI", "()Landroid/util/DisplayMetrics;"); 358 midCreateCustomCursor = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, "createCustomCursor", "([IIIII)I"); 359 midSetCustomCursor = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, "setCustomCursor", "(I)Z"); 360 midSetSystemCursor = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, "setSystemCursor", "(I)Z"); 361 362 midSupportsRelativeMouse = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, "supportsRelativeMouse", "()Z"); 363 midSetRelativeMouseEnabled = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, "setRelativeMouseEnabled", "(Z)Z"); 364 365 366 if (!midGetNativeSurface || 367 !midSetActivityTitle || !midSetWindowStyle || !midSetOrientation || !midGetContext || !midIsTablet || !midIsAndroidTV || !midInputGetInputDeviceIds || 368 !midSendMessage || !midShowTextInput || !midIsScreenKeyboardShown || 369 !midClipboardSetText || !midClipboardGetText || !midClipboardHasText || 370 !midOpenAPKExpansionInputStream || !midGetManifestEnvironmentVariables || !midGetDisplayDPI || 371 !midCreateCustomCursor || !midSetCustomCursor || !midSetSystemCursor || !midSupportsRelativeMouse || !midSetRelativeMouseEnabled || 372 !midIsChromebook || !midIsDeXMode || !midManualBackButton) { 373 __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLActivity.java?"); 374 } 375 376 fidSeparateMouseAndTouch = (*mEnv)->GetStaticFieldID(mEnv, mActivityClass, "mSeparateMouseAndTouch", "Z"); 377 378 if (!fidSeparateMouseAndTouch) { 379 __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java static fields, do you have the latest version of SDLActivity.java?"); 380 } 381 382 checkJNIReady(); 383} 384 385/* Audio initialization -- called before SDL_main() to initialize JNI bindings */ 386JNIEXPORT void JNICALL SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI)(JNIEnv* mEnv, jclass cls) 387{ 388 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "AUDIO nativeSetupJNI()"); 389 390 Android_JNI_SetupThread(); 391 392 mAudioManagerClass = (jclass)((*mEnv)->NewGlobalRef(mEnv, cls)); 393 394 midAudioOpen = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass, 395 "audioOpen", "(IZZI)I"); 396 midAudioWriteShortBuffer = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass, 397 "audioWriteShortBuffer", "([S)V"); 398 midAudioWriteByteBuffer = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass, 399 "audioWriteByteBuffer", "([B)V"); 400 midAudioClose = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass, 401 "audioClose", "()V"); 402 midCaptureOpen = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass, 403 "captureOpen", "(IZZI)I"); 404 midCaptureReadShortBuffer = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass, 405 "captureReadShortBuffer", "([SZ)I"); 406 midCaptureReadByteBuffer = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass, 407 "captureReadByteBuffer", "([BZ)I"); 408 midCaptureClose = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass, 409 "captureClose", "()V"); 410 411 if (!midAudioOpen || !midAudioWriteShortBuffer || !midAudioWriteByteBuffer || !midAudioClose || 412 !midCaptureOpen || !midCaptureReadShortBuffer || !midCaptureReadByteBuffer || !midCaptureClose) { 413 __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLAudioManager.java?"); 414 } 415 416 checkJNIReady(); 417} 418 419/* Controller initialization -- called before SDL_main() to initialize JNI bindings */ 420JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI)(JNIEnv* mEnv, jclass cls) 421{ 422 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "CONTROLLER nativeSetupJNI()"); 423 424 Android_JNI_SetupThread(); 425 426 mControllerManagerClass = (jclass)((*mEnv)->NewGlobalRef(mEnv, cls)); 427 428 midPollInputDevices = (*mEnv)->GetStaticMethodID(mEnv, mControllerManagerClass, 429 "pollInputDevices", "()V"); 430 midPollHapticDevices = (*mEnv)->GetStaticMethodID(mEnv, mControllerManagerClass, 431 "pollHapticDevices", "()V"); 432 midHapticRun = (*mEnv)->GetStaticMethodID(mEnv, mControllerManagerClass, 433 "hapticRun", "(II)V"); 434 midHapticStop = (*mEnv)->GetStaticMethodID(mEnv, mControllerManagerClass, 435 "hapticStop", "(I)V"); 436 437 if (!midPollInputDevices || !midPollHapticDevices || !midHapticRun || !midHapticStop) { 438 __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLControllerManager.java?"); 439 } 440 441 checkJNIReady(); 442} 443 444/* SDL main function prototype */ 445typedef int (*SDL_main_func)(int argc, char *argv[]); 446 447/* Start up the SDL app */ 448JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeRunMain)(JNIEnv* env, jclass cls, jstring library, jstring function, jobject array) 449{ 450 int status = -1; 451 const char *library_file; 452 void *library_handle; 453 454 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeRunMain()"); 455 456 library_file = (*env)->GetStringUTFChars(env, library, NULL); 457 library_handle = dlopen(library_file, RTLD_GLOBAL); 458 if (library_handle) { 459 const char *function_name; 460 SDL_main_func SDL_main; 461 462 function_name = (*env)->GetStringUTFChars(env, function, NULL); 463 SDL_main = (SDL_main_func)dlsym(library_handle, function_name); 464 if (SDL_main) { 465 int i; 466 int argc; 467 int len; 468 char **argv; 469 470 /* Prepare the arguments. */ 471 len = (*env)->GetArrayLength(env, array); 472 argv = SDL_stack_alloc(char*, 1 + len + 1); 473 argc = 0; 474 /* Use the name "app_process" so PHYSFS_platformCalcBaseDir() works. 475 https://bitbucket.org/MartinFelis/love-android-sdl2/issue/23/release-build-crash-on-start 476 */ 477 argv[argc++] = SDL_strdup("app_process"); 478 for (i = 0; i < len; ++i) { 479 const char* utf; 480 char* arg = NULL; 481 jstring string = (*env)->GetObjectArrayElement(env, array, i); 482 if (string) { 483 utf = (*env)->GetStringUTFChars(env, string, 0); 484 if (utf) { 485 arg = SDL_strdup(utf); 486 (*env)->ReleaseStringUTFChars(env, string, utf); 487 } 488 (*env)->DeleteLocalRef(env, string); 489 } 490 if (!arg) { 491 arg = SDL_strdup(""); 492 } 493 argv[argc++] = arg; 494 } 495 argv[argc] = NULL; 496 497 498 /* Run the application. */ 499 status = SDL_main(argc, argv); 500 501 /* Release the arguments. */ 502 for (i = 0; i < argc; ++i) { 503 SDL_free(argv[i]); 504 } 505 SDL_stack_free(argv); 506 507 } else { 508 __android_log_print(ANDROID_LOG_ERROR, "SDL", "nativeRunMain(): Couldn't find function %s in library %s", function_name, library_file); 509 } 510 (*env)->ReleaseStringUTFChars(env, function, function_name); 511 512 dlclose(library_handle); 513 514 } else { 515 __android_log_print(ANDROID_LOG_ERROR, "SDL", "nativeRunMain(): Couldn't load library %s", library_file); 516 } 517 (*env)->ReleaseStringUTFChars(env, library, library_file); 518 519 /* Do not issue an exit or the whole application will terminate instead of just the SDL thread */ 520 /* exit(status); */ 521 522 return status; 523} 524 525/* Drop file */ 526JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDropFile)( 527 JNIEnv* env, jclass jcls, 528 jstring filename) 529{ 530 const char *path = (*env)->GetStringUTFChars(env, filename, NULL); 531 SDL_SendDropFile(NULL, path); 532 (*env)->ReleaseStringUTFChars(env, filename, path); 533 SDL_SendDropComplete(NULL); 534} 535 536/* Resize */ 537JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeResize)( 538 JNIEnv* env, jclass jcls, 539 jint surfaceWidth, jint surfaceHeight, 540 jint deviceWidth, jint deviceHeight, jint format, jfloat rate) 541{ 542 Android_SetScreenResolution(surfaceWidth, surfaceHeight, deviceWidth, deviceHeight, format, rate); 543} 544 545JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeOrientationChanged)( 546 JNIEnv *env, jclass jcls, 547 jint orientation) 548{ 549 SDL_VideoDisplay *display = SDL_GetDisplay(0); 550 SDL_SendDisplayEvent(display, SDL_DISPLAYEVENT_ORIENTATION, orientation); 551} 552 553/* Paddown */ 554JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadDown)( 555 JNIEnv* env, jclass jcls, 556 jint device_id, jint keycode) 557{ 558 return Android_OnPadDown(device_id, keycode); 559} 560 561/* Padup */ 562JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadUp)( 563 JNIEnv* env, jclass jcls, 564 jint device_id, jint keycode) 565{ 566 return Android_OnPadUp(device_id, keycode); 567} 568 569/* Joy */ 570JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeJoy)( 571 JNIEnv* env, jclass jcls, 572 jint device_id, jint axis, jfloat value) 573{ 574 Android_OnJoy(device_id, axis, value); 575} 576 577/* POV Hat */ 578JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeHat)( 579 JNIEnv* env, jclass jcls, 580 jint device_id, jint hat_id, jint x, jint y) 581{ 582 Android_OnHat(device_id, hat_id, x, y); 583} 584 585 586JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick)( 587 JNIEnv* env, jclass jcls, 588 jint device_id, jstring device_name, jstring device_desc, 589 jint vendor_id, jint product_id, jboolean is_accelerometer, 590 jint button_mask, jint naxes, jint nhats, jint nballs) 591{ 592 int retval; 593 const char *name = (*env)->GetStringUTFChars(env, device_name, NULL); 594 const char *desc = (*env)->GetStringUTFChars(env, device_desc, NULL); 595 596 retval = Android_AddJoystick(device_id, name, desc, vendor_id, product_id, is_accelerometer ? SDL_TRUE : SDL_FALSE, button_mask, naxes, nhats, nballs); 597 598 (*env)->ReleaseStringUTFChars(env, device_name, name); 599 (*env)->ReleaseStringUTFChars(env, device_desc, desc); 600 601 return retval; 602} 603 604JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveJoystick)( 605 JNIEnv* env, jclass jcls, 606 jint device_id) 607{ 608 return Android_RemoveJoystick(device_id); 609} 610 611JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddHaptic)( 612 JNIEnv* env, jclass jcls, jint device_id, jstring device_name) 613{ 614 int retval; 615 const char *name = (*env)->GetStringUTFChars(env, device_name, NULL); 616 617 retval = Android_AddHaptic(device_id, name); 618 619 (*env)->ReleaseStringUTFChars(env, device_name, name); 620 621 return retval; 622} 623 624JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveHaptic)( 625 JNIEnv* env, jclass jcls, jint device_id) 626{ 627 return Android_RemoveHaptic(device_id); 628} 629 630 631/* Surface Created */ 632JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceChanged)(JNIEnv* env, jclass jcls) 633{ 634 SDL_WindowData *data; 635 SDL_VideoDevice *_this; 636 637 if (Android_Window == NULL || Android_Window->driverdata == NULL ) { 638 return; 639 } 640 641 _this = SDL_GetVideoDevice(); 642 data = (SDL_WindowData *) Android_Window->driverdata; 643 644 /* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */ 645 if (data->egl_surface == EGL_NO_SURFACE) { 646 if(data->native_window) { 647 ANativeWindow_release(data->native_window); 648 } 649 data->native_window = Android_JNI_GetNativeWindow(); 650 data->egl_surface = SDL_EGL_CreateSurface(_this, (NativeWindowType) data->native_window); 651 } 652 653 /* GL Context handling is done in the event loop because this function is run from the Java thread */ 654 655} 656 657/* Surface Destroyed */ 658JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceDestroyed)(JNIEnv* env, jclass jcls) 659{ 660 /* We have to clear the current context and destroy the egl surface here 661 * Otherwise there's BAD_NATIVE_WINDOW errors coming from eglCreateWindowSurface on resume 662 * Ref: http://stackoverflow.com/questions/8762589/eglcreatewindowsurface-on-ics-and-switching-from-2d-to-3d 663 */ 664 SDL_WindowData *data; 665 SDL_VideoDevice *_this; 666 667 if (Android_Window == NULL || Android_Window->driverdata == NULL ) { 668 return; 669 } 670 671 _this = SDL_GetVideoDevice(); 672 data = (SDL_WindowData *) Android_Window->driverdata; 673 674 if (data->egl_surface != EGL_NO_SURFACE) { 675 SDL_EGL_MakeCurrent(_this, NULL, NULL); 676 SDL_EGL_DestroySurface(_this, data->egl_surface); 677 data->egl_surface = EGL_NO_SURFACE; 678 } 679 680 /* GL Context handling is done in the event loop because this function is run from the Java thread */ 681 682} 683 684/* Keydown */ 685JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyDown)( 686 JNIEnv* env, jclass jcls, 687 jint keycode) 688{ 689 Android_OnKeyDown(keycode); 690} 691 692/* Keyup */ 693JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyUp)( 694 JNIEnv* env, jclass jcls, 695 jint keycode) 696{ 697 Android_OnKeyUp(keycode); 698} 699 700/* Keyboard Focus Lost */ 701JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyboardFocusLost)( 702 JNIEnv* env, jclass jcls) 703{ 704 /* Calling SDL_StopTextInput will take care of hiding the keyboard and cleaning up the DummyText widget */ 705 SDL_StopTextInput(); 706} 707 708 709/* Touch */ 710JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeTouch)( 711 JNIEnv* env, jclass jcls, 712 jint touch_device_id_in, jint pointer_finger_id_in, 713 jint action, jfloat x, jfloat y, jfloat p) 714{ 715 Android_OnTouch(touch_device_id_in, pointer_finger_id_in, action, x, y, p); 716} 717 718/* Mouse */ 719JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeMouse)( 720 JNIEnv* env, jclass jcls, 721 jint button, jint action, jfloat x, jfloat y, jboolean relative) 722{ 723 Android_OnMouse(button, action, x, y, relative); 724} 725 726/* Accelerometer */ 727JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeAccel)( 728 JNIEnv* env, jclass jcls, 729 jfloat x, jfloat y, jfloat z) 730{ 731 fLastAccelerometer[0] = x; 732 fLastAccelerometer[1] = y; 733 fLastAccelerometer[2] = z; 734 bHasNewData = SDL_TRUE; 735} 736 737/* Clipboard */ 738JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeClipboardChanged)( 739 JNIEnv* env, jclass jcls) 740{ 741 SDL_SendClipboardUpdate(); 742} 743 744/* Low memory */ 745JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeLowMemory)( 746 JNIEnv* env, jclass cls) 747{ 748 SDL_SendAppEvent(SDL_APP_LOWMEMORY); 749} 750 751/* Quit */ 752JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeQuit)( 753 JNIEnv* env, jclass cls) 754{ 755 /* Discard previous events. The user should have handled state storage 756 * in SDL_APP_WILLENTERBACKGROUND. After nativeQuit() is called, no 757 * events other than SDL_QUIT and SDL_APP_TERMINATING should fire */ 758 SDL_FlushEvents(SDL_FIRSTEVENT, SDL_LASTEVENT); 759 /* Inject a SDL_QUIT event */ 760 SDL_SendQuit(); 761 SDL_SendAppEvent(SDL_APP_TERMINATING); 762 /* Resume the event loop so that the app can catch SDL_QUIT which 763 * should now be the top event in the event queue. */ 764 if (!SDL_SemValue(Android_ResumeSem)) SDL_SemPost(Android_ResumeSem); 765} 766 767/* Pause */ 768JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativePause)( 769 JNIEnv* env, jclass cls) 770{ 771 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativePause()"); 772 773 if (Android_Window) { 774 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_LOST, 0, 0); 775 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_MINIMIZED, 0, 0); 776 SDL_SendAppEvent(SDL_APP_WILLENTERBACKGROUND); 777 SDL_SendAppEvent(SDL_APP_DIDENTERBACKGROUND); 778 779 /* *After* sending the relevant events, signal the pause semaphore 780 * so the event loop knows to pause and (optionally) block itself */ 781 if (!SDL_SemValue(Android_PauseSem)) SDL_SemPost(Android_PauseSem); 782 } 783} 784 785/* Resume */ 786JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeResume)( 787 JNIEnv* env, jclass cls) 788{ 789 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeResume()"); 790 791 if (Android_Window) { 792 SDL_SendAppEvent(SDL_APP_WILLENTERFOREGROUND); 793 SDL_SendAppEvent(SDL_APP_DIDENTERFOREGROUND); 794 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_GAINED, 0, 0); 795 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_RESTORED, 0, 0); 796 /* Signal the resume semaphore so the event loop knows to resume and restore the GL Context 797 * We can't restore the GL Context here because it needs to be done on the SDL main thread 798 * and this function will be called from the Java thread instead. 799 */ 800 if (!SDL_SemValue(Android_ResumeSem)) SDL_SemPost(Android_ResumeSem); 801 } 802} 803 804JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeCommitText)( 805 JNIEnv* env, jclass cls, 806 jstring text, jint newCursorPosition) 807{ 808 const char *utftext = (*env)->GetStringUTFChars(env, text, NULL); 809 810 SDL_SendKeyboardText(utftext); 811 812 (*env)->ReleaseStringUTFChars(env, text, utftext); 813} 814 815JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeGenerateScancodeForUnichar)( 816 JNIEnv* env, jclass cls, 817 jchar chUnicode) 818{ 819 SDL_Scancode code = SDL_SCANCODE_UNKNOWN; 820 uint16_t mod = 0; 821 822 // We do not care about bigger than 127. 823 if (chUnicode < 127) { 824 AndroidKeyInfo info = unicharToAndroidKeyInfoTable[chUnicode]; 825 code = info.code; 826 mod = info.mod; 827 } 828 829 if (mod & KMOD_SHIFT) { 830 /* If character uses shift, press shift down */ 831 SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_LSHIFT); 832 } 833 834 /* send a keydown and keyup even for the character */ 835 SDL_SendKeyboardKey(SDL_PRESSED, code); 836 SDL_SendKeyboardKey(SDL_RELEASED, code); 837 838 if (mod & KMOD_SHIFT) { 839 /* If character uses shift, press shift back up */ 840 SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_LSHIFT); 841 } 842} 843 844 845JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeSetComposingText)( 846 JNIEnv* env, jclass cls, 847 jstring text, jint newCursorPosition) 848{ 849 const char *utftext = (*env)->GetStringUTFChars(env, text, NULL); 850 851 SDL_SendEditingText(utftext, 0, 0); 852 853 (*env)->ReleaseStringUTFChars(env, text, utftext); 854} 855 856JNIEXPORT jstring JNICALL SDL_JAVA_INTERFACE(nativeGetHint)( 857 JNIEnv* env, jclass cls, 858 jstring name) 859{ 860 const char *utfname = (*env)->GetStringUTFChars(env, name, NULL); 861 const char *hint = SDL_GetHint(utfname); 862 863 jstring result = (*env)->NewStringUTF(env, hint); 864 (*env)->ReleaseStringUTFChars(env, name, utfname); 865 866 return result; 867} 868 869JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetenv)( 870 JNIEnv* env, jclass cls, 871 jstring name, jstring value) 872{ 873 const char *utfname = (*env)->GetStringUTFChars(env, name, NULL); 874 const char *utfvalue = (*env)->GetStringUTFChars(env, value, NULL); 875 876 SDL_setenv(utfname, utfvalue, 1); 877 878 (*env)->ReleaseStringUTFChars(env, name, utfname); 879 (*env)->ReleaseStringUTFChars(env, value, utfvalue); 880 881} 882 883/******************************************************************************* 884 Functions called by SDL into Java 885*******************************************************************************/ 886 887static int s_active = 0; 888struct LocalReferenceHolder 889{ 890 JNIEnv *m_env; 891 const char *m_func; 892}; 893 894static struct LocalReferenceHolder LocalReferenceHolder_Setup(const char *func) 895{ 896 struct LocalReferenceHolder refholder; 897 refholder.m_env = NULL; 898 refholder.m_func = func; 899#ifdef DEBUG_JNI 900 SDL_Log("Entering function %s", func); 901#endif 902 return refholder; 903} 904 905static SDL_bool LocalReferenceHolder_Init(struct LocalReferenceHolder *refholder, JNIEnv *env) 906{ 907 const int capacity = 16; 908 if ((*env)->PushLocalFrame(env, capacity) < 0) { 909 SDL_SetError("Failed to allocate enough JVM local references"); 910 return SDL_FALSE; 911 } 912 ++s_active; 913 refholder->m_env = env; 914 return SDL_TRUE; 915} 916 917static void LocalReferenceHolder_Cleanup(struct LocalReferenceHolder *refholder) 918{ 919#ifdef DEBUG_JNI 920 SDL_Log("Leaving function %s", refholder->m_func); 921#endif 922 if (refholder->m_env) { 923 JNIEnv* env = refholder->m_env; 924 (*env)->PopLocalFrame(env, NULL); 925 --s_active; 926 } 927} 928 929static SDL_bool LocalReferenceHolder_IsActive(void) 930{ 931 return s_active > 0; 932} 933 934ANativeWindow* Android_JNI_GetNativeWindow(void) 935{ 936 ANativeWindow* anw; 937 jobject s; 938 JNIEnv *env = Android_JNI_GetEnv(); 939 940 s = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetNativeSurface); 941 anw = ANativeWindow_fromSurface(env, s); 942 (*env)->DeleteLocalRef(env, s); 943 944 return anw; 945} 946 947void Android_JNI_SetActivityTitle(const char *title) 948{ 949 JNIEnv *mEnv = Android_JNI_GetEnv(); 950 951 jstring jtitle = (jstring)((*mEnv)->NewStringUTF(mEnv, title)); 952 (*mEnv)->CallStaticBooleanMethod(mEnv, mActivityClass, midSetActivityTitle, jtitle); 953 (*mEnv)->DeleteLocalRef(mEnv, jtitle); 954} 955 956void Android_JNI_SetWindowStyle(SDL_bool fullscreen) 957{ 958 JNIEnv *mEnv = Android_JNI_GetEnv(); 959 (*mEnv)->CallStaticVoidMethod(mEnv, mActivityClass, midSetWindowStyle, fullscreen ? 1 : 0); 960} 961 962void Android_JNI_SetOrientation(int w, int h, int resizable, const char *hint) 963{ 964 JNIEnv *mEnv = Android_JNI_GetEnv(); 965 966 jstring jhint = (jstring)((*mEnv)->NewStringUTF(mEnv, (hint ? hint : ""))); 967 (*mEnv)->CallStaticVoidMethod(mEnv, mActivityClass, midSetOrientation, w, h, (resizable? 1 : 0), jhint); 968 (*mEnv)->DeleteLocalRef(mEnv, jhint); 969} 970 971SDL_bool Android_JNI_GetAccelerometerValues(float values[3]) 972{ 973 int i; 974 SDL_bool retval = SDL_FALSE; 975 976 if (bHasNewData) { 977 for (i = 0; i < 3; ++i) { 978 values[i] = fLastAccelerometer[i]; 979 } 980 bHasNewData = SDL_FALSE; 981 retval = SDL_TRUE; 982 } 983 984 return retval; 985} 986 987static void Android_JNI_ThreadDestroyed(void* value) 988{ 989 /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */ 990 JNIEnv *env = (JNIEnv*) value; 991 if (env != NULL) { 992 (*mJavaVM)->DetachCurrentThread(mJavaVM); 993 pthread_setspecific(mThreadKey, NULL); 994 } 995} 996 997JNIEnv* Android_JNI_GetEnv(void) 998{ 999 /* From http://developer.android.com/guide/practices/jni.html 1000 * All threads are Linux threads, scheduled by the kernel. 1001 * They're usually started from managed code (using Thread.start), but they can also be created elsewhere and then 1002 * attached to the JavaVM. For example, a thread started with pthread_create can be attached with the 1003 * JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a thread is attached, it has no JNIEnv, 1004 * and cannot make JNI calls. 1005 * Attaching a natively-created thread causes a java.lang.Thread object to be constructed and added to the "main" 1006 * ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread on an already-attached thread 1007 * is a no-op. 1008 * Note: You can call this function any number of times for the same thread, there's no harm in it 1009 */ 1010 1011 JNIEnv *env; 1012 int status = (*mJavaVM)->AttachCurrentThread(mJavaVM, &env, NULL); 1013 if(status < 0) { 1014 LOGE("failed to attach current thread"); 1015 return 0; 1016 } 1017 1018 /* From http://developer.android.com/guide/practices/jni.html 1019 * Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward, 1020 * in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be 1021 * called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific 1022 * to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.) 1023 * Note: The destructor is not called unless the stored value is != NULL 1024 * Note: You can call this function any number of times for the same thread, there's no harm in it 1025 * (except for some lost CPU cycles) 1026 */ 1027 pthread_setspecific(mThreadKey, (void*) env); 1028 1029 return env; 1030} 1031 1032int Android_JNI_SetupThread(void) 1033{ 1034 Android_JNI_GetEnv(); 1035 return 1; 1036} 1037 1038/* 1039 * Audio support 1040 */ 1041static jboolean audioBuffer16Bit = JNI_FALSE; 1042static jobject audioBuffer = NULL; 1043static void* audioBufferPinned = NULL; 1044static jboolean captureBuffer16Bit = JNI_FALSE; 1045static jobject captureBuffer = NULL; 1046 1047int Android_JNI_OpenAudioDevice(int iscapture, int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames) 1048{ 1049 jboolean audioBufferStereo; 1050 int audioBufferFrames; 1051 jobject jbufobj = NULL; 1052 jboolean isCopy; 1053 1054 JNIEnv *env = Android_JNI_GetEnv(); 1055 1056 if (!env) { 1057 LOGE("callback_handler: failed to attach current thread"); 1058 } 1059 Android_JNI_SetupThread(); 1060 1061 audioBufferStereo = channelCount > 1; 1062 1063 if (iscapture) { 1064 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device for capture"); 1065 captureBuffer16Bit = is16Bit; 1066 if ((*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureOpen, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames) != 0) { 1067 /* Error during audio initialization */ 1068 __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: error on AudioRecord initialization!"); 1069 return 0; 1070 } 1071 } else { 1072 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device for output"); 1073 audioBuffer16Bit = is16Bit; 1074 if ((*env)->CallStaticIntMethod(env, mAudioManagerClass, midAudioOpen, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames) != 0) { 1075 /* Error during audio initialization */ 1076 __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: error on AudioTrack initialization!"); 1077 return 0; 1078 } 1079 } 1080 1081 /* Allocating the audio buffer from the Java side and passing it as the return value for audioInit no longer works on 1082 * Android >= 4.2 due to a "stale global reference" error. So now we allocate this buffer directly from this side. */ 1083 1084 if (is16Bit) { 1085 jshortArray audioBufferLocal = (*env)->NewShortArray(env, desiredBufferFrames * (audioBufferStereo ? 2 : 1)); 1086 if (audioBufferLocal) { 1087 jbufobj = (*env)->NewGlobalRef(env, audioBufferLocal); 1088 (*env)->DeleteLocalRef(env, audioBufferLocal); 1089 } 1090 } 1091 else { 1092 jbyteArray audioBufferLocal = (*env)->NewByteArray(env, desiredBufferFrames * (audioBufferStereo ? 2 : 1)); 1093 if (audioBufferLocal) { 1094 jbufobj = (*env)->NewGlobalRef(env, audioBufferLocal); 1095 (*env)->DeleteLocalRef(env, audioBufferLocal); 1096 } 1097 } 1098 1099 if (jbufobj == NULL) { 1100 __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: could not allocate an audio buffer!"); 1101 return 0; 1102 } 1103 1104 if (iscapture) { 1105 captureBuffer = jbufobj; 1106 } else { 1107 audioBuffer = jbufobj; 1108 } 1109 1110 isCopy = JNI_FALSE; 1111 1112 if (is16Bit) { 1113 if (!iscapture) { 1114 audioBufferPinned = (*env)->GetShortArrayElements(env, (jshortArray)audioBuffer, &isCopy); 1115 } 1116 audioBufferFrames = (*env)->GetArrayLength(env, (jshortArray)audioBuffer); 1117 } else { 1118 if (!iscapture) { 1119 audioBufferPinned = (*env)->GetByteArrayElements(env, (jbyteArray)audioBuffer, &isCopy); 1120 } 1121 audioBufferFrames = (*env)->GetArrayLength(env, (jbyteArray)audioBuffer); 1122 } 1123 1124 if (audioBufferStereo) { 1125 audioBufferFrames /= 2; 1126 } 1127 1128 return audioBufferFrames; 1129} 1130 1131int Android_JNI_GetDisplayDPI(float *ddpi, float *xdpi, float *ydpi) 1132{ 1133 JNIEnv *env = Android_JNI_GetEnv(); 1134 1135 jobject jDisplayObj = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetDisplayDPI); 1136 jclass jDisplayClass = (*env)->GetObjectClass(env, jDisplayObj); 1137 1138 jfieldID fidXdpi = (*env)->GetFieldID(env, jDisplayClass, "xdpi", "F"); 1139 jfieldID fidYdpi = (*env)->GetFieldID(env, jDisplayClass, "ydpi", "F"); 1140 jfieldID fidDdpi = (*env)->GetFieldID(env, jDisplayClass, "densityDpi", "I"); 1141 1142 float nativeXdpi = (*env)->GetFloatField(env, jDisplayObj, fidXdpi); 1143 float nativeYdpi = (*env)->GetFloatField(env, jDisplayObj, fidYdpi); 1144 int nativeDdpi = (*env)->GetIntField(env, jDisplayObj, fidDdpi); 1145 1146 1147 (*env)->DeleteLocalRef(env, jDisplayObj); 1148 (*env)->DeleteLocalRef(env, jDisplayClass); 1149 1150 if (ddpi) { 1151 *ddpi = (float)nativeDdpi; 1152 } 1153 if (xdpi) { 1154 *xdpi = nativeXdpi; 1155 } 1156 if (ydpi) { 1157 *ydpi = nativeYdpi; 1158 } 1159 1160 return 0; 1161} 1162 1163void * Android_JNI_GetAudioBuffer(void) 1164{ 1165 return audioBufferPinned; 1166} 1167 1168void Android_JNI_WriteAudioBuffer(void) 1169{ 1170 JNIEnv *mAudioEnv = Android_JNI_GetEnv(); 1171 1172 if (audioBuffer16Bit) { 1173 (*mAudioEnv)->ReleaseShortArrayElements(mAudioEnv, (jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT); 1174 (*mAudioEnv)->CallStaticVoidMethod(mAudioEnv, mAudioManagerClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer); 1175 } else { 1176 (*mAudioEnv)->ReleaseByteArrayElements(mAudioEnv, (jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT); 1177 (*mAudioEnv)->CallStaticVoidMethod(mAudioEnv, mAudioManagerClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer); 1178 } 1179 1180 /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */ 1181} 1182 1183int Android_JNI_CaptureAudioBuffer(void *buffer, int buflen) 1184{ 1185 JNIEnv *env = Android_JNI_GetEnv(); 1186 jboolean isCopy = JNI_FALSE; 1187 jint br; 1188 1189 if (captureBuffer16Bit) { 1190 SDL_assert((*env)->GetArrayLength(env, (jshortArray)captureBuffer) == (buflen / 2)); 1191 br = (*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureReadShortBuffer, (jshortArray)captureBuffer, JNI_TRUE); 1192 if (br > 0) { 1193 jshort *ptr = (*env)->GetShortArrayElements(env, (jshortArray)captureBuffer, &isCopy); 1194 br *= 2; 1195 SDL_memcpy(buffer, ptr, br); 1196 (*env)->ReleaseShortArrayElements(env, (jshortArray)captureBuffer, (jshort *)ptr, JNI_ABORT); 1197 } 1198 } else { 1199 SDL_assert((*env)->GetArrayLength(env, (jshortArray)captureBuffer) == buflen); 1200 br = (*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureReadByteBuffer, (jbyteArray)captureBuffer, JNI_TRUE); 1201 if (br > 0) { 1202 jbyte *ptr = (*env)->GetByteArrayElements(env, (jbyteArray)captureBuffer, &isCopy); 1203 SDL_memcpy(buffer, ptr, br); 1204 (*env)->ReleaseByteArrayElements(env, (jbyteArray)captureBuffer, (jbyte *)ptr, JNI_ABORT); 1205 } 1206 } 1207 1208 return (int) br; 1209} 1210 1211void Android_JNI_FlushCapturedAudio(void) 1212{ 1213 JNIEnv *env = Android_JNI_GetEnv(); 1214#if 0 /* !!! FIXME: this needs API 23, or it'll do blocking reads and never end. */ 1215 if (captureBuffer16Bit) { 1216 const jint len = (*env)->GetArrayLength(env, (jshortArray)captureBuffer); 1217 while ((*env)->CallStaticIntMethod(env, mActivityClass, midCaptureReadShortBuffer, (jshortArray)captureBuffer, JNI_FALSE) == len) { /* spin */ } 1218 } else { 1219 const jint len = (*env)->GetArrayLength(env, (jbyteArray)captureBuffer); 1220 while ((*env)->CallStaticIntMethod(env, mActivityClass, midCaptureReadByteBuffer, (jbyteArray)captureBuffer, JNI_FALSE) == len) { /* spin */ } 1221 } 1222#else 1223 if (captureBuffer16Bit) { 1224 (*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureReadShortBuffer, (jshortArray)captureBuffer, JNI_FALSE); 1225 } else { 1226 (*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureReadByteBuffer, (jbyteArray)captureBuffer, JNI_FALSE); 1227 } 1228#endif 1229} 1230 1231void Android_JNI_CloseAudioDevice(const int iscapture) 1232{ 1233 JNIEnv *env = Android_JNI_GetEnv(); 1234 1235 if (iscapture) { 1236 (*env)->CallStaticVoidMethod(env, mAudioManagerClass, midCaptureClose); 1237 if (captureBuffer) { 1238 (*env)->DeleteGlobalRef(env, captureBuffer); 1239 captureBuffer = NULL; 1240 } 1241 } else { 1242 (*env)->CallStaticVoidMethod(env, mAudioManagerClass, midAudioClose); 1243 if (audioBuffer) { 1244 (*env)->DeleteGlobalRef(env, audioBuffer); 1245 audioBuffer = NULL; 1246 audioBufferPinned = NULL; 1247 } 1248 } 1249} 1250 1251/* Test for an exception and call SDL_SetError with its detail if one occurs */ 1252/* If the parameter silent is truthy then SDL_SetError() will not be called. */ 1253static SDL_bool Android_JNI_ExceptionOccurred(SDL_bool silent) 1254{ 1255 JNIEnv *mEnv = Android_JNI_GetEnv(); 1256 jthrowable exception; 1257 1258 SDL_assert(LocalReferenceHolder_IsActive()); 1259 1260 exception = (*mEnv)->ExceptionOccurred(mEnv); 1261 if (exception != NULL) { 1262 jmethodID mid; 1263 1264 /* Until this happens most JNI operations have undefined behaviour */ 1265 (*mEnv)->ExceptionClear(mEnv); 1266 1267 if (!silent) { 1268 jclass exceptionClass = (*mEnv)->GetObjectClass(mEnv, exception); 1269 jclass classClass = (*mEnv)->FindClass(mEnv, "java/lang/Class"); 1270 jstring exceptionName; 1271 const char* exceptionNameUTF8; 1272 jstring exceptionMessage; 1273 1274 mid = (*mEnv)->GetMethodID(mEnv, classClass, "getName", "()Ljava/lang/String;"); 1275 exceptionName = (jstring)(*mEnv)->CallObjectMethod(mEnv, exceptionClass, mid); 1276 exceptionNameUTF8 = (*mEnv)->GetStringUTFChars(mEnv, exceptionName, 0); 1277 1278 mid = (*mEnv)->GetMethodID(mEnv, exceptionClass, "getMessage", "()Ljava/lang/String;"); 1279 exceptionMessage = (jstring)(*mEnv)->CallObjectMethod(mEnv, exception, mid); 1280 1281 if (exceptionMessage != NULL) { 1282 const char* exceptionMessageUTF8 = (*mEnv)->GetStringUTFChars(mEnv, exceptionMessage, 0); 1283 SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8); 1284 (*mEnv)->ReleaseStringUTFChars(mEnv, exceptionMessage, exceptionMessageUTF8); 1285 } else { 1286 SDL_SetError("%s", exceptionNameUTF8); 1287 } 1288 1289 (*mEnv)->ReleaseStringUTFChars(mEnv, exceptionName, exceptionNameUTF8); 1290 } 1291 1292 return SDL_TRUE; 1293 } 1294 1295 return SDL_FALSE; 1296} 1297 1298static int Internal_Android_JNI_FileOpen(SDL_RWops* ctx) 1299{ 1300 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); 1301 1302 int result = 0; 1303 1304 jmethodID mid; 1305 jobject context; 1306 jobject assetManager; 1307 jobject inputStream; 1308 jclass channels; 1309 jobject readableByteChannel; 1310 jstring fileNameJString; 1311 jobject fd; 1312 jclass fdCls; 1313 jfieldID descriptor; 1314 1315 JNIEnv *mEnv = Android_JNI_GetEnv(); 1316 if (!LocalReferenceHolder_Init(&refs, mEnv)) { 1317 goto failure; 1318 } 1319 1320 fileNameJString = (jstring)ctx->hidden.androidio.fileNameRef; 1321 ctx->hidden.androidio.position = 0; 1322 1323 /* context = SDLActivity.getContext(); */ 1324 context = (*mEnv)->CallStaticObjectMethod(mEnv, mActivityClass, midGetContext); 1325 1326 /* assetManager = context.getAssets(); */ 1327 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, context), 1328 "getAssets", "()Landroid/content/res/AssetManager;"); 1329 assetManager = (*mEnv)->CallObjectMethod(mEnv, context, mid); 1330 1331 /* First let's try opening the file to obtain an AssetFileDescriptor. 1332 * This method reads the files directly from the APKs using standard *nix calls 1333 */ 1334 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, assetManager), "openFd", "(Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;"); 1335 inputStream = (*mEnv)->CallObjectMethod(mEnv, assetManager, mid, fileNameJString); 1336 if (Android_JNI_ExceptionOccurred(SDL_TRUE)) { 1337 goto fallback; 1338 } 1339 1340 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getStartOffset", "()J"); 1341 ctx->hidden.androidio.offset = (*mEnv)->CallLongMethod(mEnv, inputStream, mid); 1342 if (Android_JNI_ExceptionOccurred(SDL_TRUE)) { 1343 goto fallback; 1344 } 1345 1346 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getDeclaredLength", "()J"); 1347 ctx->hidden.androidio.size = (*mEnv)->CallLongMethod(mEnv, inputStream, mid); 1348 if (Android_JNI_ExceptionOccurred(SDL_TRUE)) { 1349 goto fallback; 1350 } 1351 1352 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getFileDescriptor", "()Ljava/io/FileDescriptor;"); 1353 fd = (*mEnv)->CallObjectMethod(mEnv, inputStream, mid); 1354 fdCls = (*mEnv)->GetObjectClass(mEnv, fd); 1355 descriptor = (*mEnv)->GetFieldID(mEnv, fdCls, "descriptor", "I"); 1356 ctx->hidden.androidio.fd = (*mEnv)->GetIntField(mEnv, fd, descriptor); 1357 ctx->hidden.androidio.assetFileDescriptorRef = (*mEnv)->NewGlobalRef(mEnv, inputStream); 1358 1359 /* Seek to the correct offset in the file. */ 1360 lseek(ctx->hidden.androidio.fd, (off_t)ctx->hidden.androidio.offset, SEEK_SET); 1361 1362 if (0) { 1363fallback: 1364 /* Disabled log message because of spam on the Nexus 7 */ 1365 /* __android_log_print(ANDROID_LOG_DEBUG, "SDL", "Falling back to legacy InputStream method for opening file"); */ 1366 1367 /* Try the old method using InputStream */ 1368 ctx->hidden.androidio.assetFileDescriptorRef = NULL; 1369 1370 /* inputStream = assetManager.open(<filename>); */ 1371 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, assetManager), 1372 "open", "(Ljava/lang/String;I)Ljava/io/InputStream;"); 1373 inputStream = (*mEnv)->CallObjectMethod(mEnv, assetManager, mid, fileNameJString, 1 /* ACCESS_RANDOM */); 1374 if (Android_JNI_ExceptionOccurred(SDL_FALSE)) { 1375 /* Try fallback to APK expansion files */ 1376 inputStream = (*mEnv)->CallStaticObjectMethod(mEnv, mActivityClass, midOpenAPKExpansionInputStream, fileNameJString); 1377 1378 /* Exception is checked first because it always needs to be cleared. 1379 * If no exception occurred then the last SDL error message is kept. 1380 */ 1381 if (Android_JNI_ExceptionOccurred(SDL_FALSE) || !inputStream) { 1382 goto failure; 1383 } 1384 } 1385 1386 ctx->hidden.androidio.inputStreamRef = (*mEnv)->NewGlobalRef(mEnv, inputStream); 1387 1388 /* Despite all the visible documentation on [Asset]InputStream claiming 1389 * that the .available() method is not guaranteed to return the entire file 1390 * size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ... 1391 * android/apis/content/ReadAsset.java imply that Android's 1392 * AssetInputStream.available() /will/ always return the total file size 1393 */ 1394 1395 /* size = inputStream.available(); */ 1396 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), 1397 "available", "()I"); 1398 ctx->hidden.androidio.size = (long)(*mEnv)->CallIntMethod(mEnv, inputStream, mid); 1399 if (Android_JNI_ExceptionOccurred(SDL_FALSE)) { 1400 goto failure; 1401 } 1402 1403 /* readableByteChannel = Channels.newChannel(inputStream); */ 1404 channels = (*mEnv)->FindClass(mEnv, "java/nio/channels/Channels"); 1405 mid = (*mEnv)->GetStaticMethodID(mEnv, channels, 1406 "newChannel", 1407 "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;"); 1408 readableByteChannel = (*mEnv)->CallStaticObjectMethod( 1409 mEnv, channels, mid, inputStream); 1410 if (Android_JNI_ExceptionOccurred(SDL_FALSE)) { 1411 goto failure; 1412 } 1413 1414 ctx->hidden.androidio.readableByteChannelRef = 1415 (*mEnv)->NewGlobalRef(mEnv, readableByteChannel); 1416 1417 /* Store .read id for reading purposes */ 1418 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, readableByteChannel), 1419 "read", "(Ljava/nio/ByteBuffer;)I"); 1420 ctx->hidden.androidio.readMethod = mid; 1421 } 1422 1423 if (0) { 1424failure: 1425 result = -1; 1426 1427 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.fileNameRef); 1428 1429 if(ctx->hidden.androidio.inputStreamRef != NULL) { 1430 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.inputStreamRef); 1431 } 1432 1433 if(ctx->hidden.androidio.readableByteChannelRef != NULL) { 1434 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.readableByteChannelRef); 1435 } 1436 1437 if(ctx->hidden.androidio.assetFileDescriptorRef != NULL) { 1438 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.assetFileDescriptorRef); 1439 } 1440 1441 } 1442 1443 LocalReferenceHolder_Cleanup(&refs); 1444 return result; 1445} 1446 1447int Android_JNI_FileOpen(SDL_RWops* ctx, 1448 const char* fileName, const char* mode) 1449{ 1450 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); 1451 JNIEnv *mEnv = Android_JNI_GetEnv(); 1452 int retval; 1453 jstring fileNameJString; 1454 1455 if (!LocalReferenceHolder_Init(&refs, mEnv)) { 1456 LocalReferenceHolder_Cleanup(&refs); 1457 return -1; 1458 } 1459 1460 if (!ctx) { 1461 LocalReferenceHolder_Cleanup(&refs); 1462 return -1; 1463 } 1464 1465 fileNameJString = (*mEnv)->NewStringUTF(mEnv, fileName); 1466 ctx->hidden.androidio.fileNameRef = (*mEnv)->NewGlobalRef(mEnv, fileNameJString); 1467 ctx->hidden.androidio.inputStreamRef = NULL; 1468 ctx->hidden.androidio.readableByteChannelRef = NULL; 1469 ctx->hidden.androidio.readMethod = NULL; 1470 ctx->hidden.androidio.assetFileDescriptorRef = NULL; 1471 1472 retval = Internal_Android_JNI_FileOpen(ctx); 1473 LocalReferenceHolder_Cleanup(&refs); 1474 return retval; 1475} 1476 1477size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer, 1478 size_t size, size_t maxnum) 1479{ 1480 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); 1481 1482 if (ctx->hidden.androidio.assetFileDescriptorRef) { 1483 size_t bytesMax = size * maxnum; 1484 size_t result; 1485 if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && ctx->hidden.androidio.position + bytesMax > ctx->hidden.androidio.size) { 1486 bytesMax = ctx->hidden.androidio.size - ctx->hidden.androidio.position; 1487 } 1488 result = read(ctx->hidden.androidio.fd, buffer, bytesMax ); 1489 if (result > 0) { 1490 ctx->hidden.androidio.position += result; 1491 LocalReferenceHolder_Cleanup(&refs); 1492 return result / size; 1493 } 1494 LocalReferenceHolder_Cleanup(&refs); 1495 return 0; 1496 } else { 1497 jlong bytesRemaining = (jlong) (size * maxnum); 1498 jlong bytesMax = (jlong) (ctx->hidden.androidio.size - ctx->hidden.androidio.position); 1499 int bytesRead = 0; 1500 JNIEnv *mEnv; 1501 jobject readableByteChannel; 1502 jmethodID readMethod; 1503 jobject byteBuffer; 1504 1505 /* Don't read more bytes than those that remain in the file, otherwise we get an exception */ 1506 if (bytesRemaining > bytesMax) bytesRemaining = bytesMax; 1507 1508 mEnv = Android_JNI_GetEnv(); 1509 if (!LocalReferenceHolder_Init(&refs, mEnv)) { 1510 LocalReferenceHolder_Cleanup(&refs); 1511 return 0; 1512 } 1513 1514 readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannelRef; 1515 readMethod = (jmethodID)ctx->hidden.androidio.readMethod; 1516 byteBuffer = (*mEnv)->NewDirectByteBuffer(mEnv, buffer, bytesRemaining); 1517 1518 while (bytesRemaining > 0) { 1519 /* result = readableByteChannel.read(...); */ 1520 int result = (*mEnv)->CallIntMethod(mEnv, readableByteChannel, readMethod, byteBuffer); 1521 1522 if (Android_JNI_ExceptionOccurred(SDL_FALSE)) { 1523 LocalReferenceHolder_Cleanup(&refs); 1524 return 0; 1525 } 1526 1527 if (result < 0) { 1528 break; 1529 } 1530 1531 bytesRemaining -= result; 1532 bytesRead += result; 1533 ctx->hidden.androidio.position += result; 1534 } 1535 LocalReferenceHolder_Cleanup(&refs); 1536 return bytesRead / size; 1537 } 1538} 1539 1540size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer, 1541 size_t size, size_t num) 1542{ 1543 SDL_SetError("Cannot write to Android package filesystem"); 1544 return 0; 1545} 1546 1547static int Internal_Android_JNI_FileClose(SDL_RWops* ctx, SDL_bool release) 1548{ 1549 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); 1550 1551 int result = 0; 1552 JNIEnv *mEnv = Android_JNI_GetEnv(); 1553 1554 if (!LocalReferenceHolder_Init(&refs, mEnv)) { 1555 LocalReferenceHolder_Cleanup(&refs); 1556 return SDL_SetError("Failed to allocate enough JVM local references"); 1557 } 1558 1559 if (ctx) { 1560 if (release) { 1561 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.fileNameRef); 1562 } 1563 1564 if (ctx->hidden.androidio.assetFileDescriptorRef) { 1565 jobject inputStream = (jobject)ctx->hidden.androidio.assetFileDescriptorRef; 1566 jmethodID mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), 1567 "close", "()V"); 1568 (*mEnv)->CallVoidMethod(mEnv, inputStream, mid); 1569 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.assetFileDescriptorRef); 1570 if (Android_JNI_ExceptionOccurred(SDL_FALSE)) { 1571 result = -1; 1572 } 1573 } 1574 else { 1575 jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef; 1576 1577 /* inputStream.close(); */ 1578 jmethodID mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), 1579 "close", "()V"); 1580 (*mEnv)->CallVoidMethod(mEnv, inputStream, mid); 1581 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.inputStreamRef); 1582 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.readableByteChannelRef); 1583 if (Android_JNI_ExceptionOccurred(SDL_FALSE)) { 1584 result = -1; 1585 } 1586 } 1587 1588 if (release) { 1589 SDL_FreeRW(ctx); 1590 } 1591 } 1592 1593 LocalReferenceHolder_Cleanup(&refs); 1594 return result; 1595} 1596 1597 1598Sint64 Android_JNI_FileSize(SDL_RWops* ctx) 1599{ 1600 return ctx->hidden.androidio.size; 1601} 1602 1603Sint64 Android_JNI_FileSeek(SDL_RWops* ctx, Sint64 offset, int whence) 1604{ 1605 if (ctx->hidden.androidio.assetFileDescriptorRef) { 1606 off_t ret; 1607 switch (whence) { 1608 case RW_SEEK_SET: 1609 if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size; 1610 offset += ctx->hidden.androidio.offset; 1611 break; 1612 case RW_SEEK_CUR: 1613 offset += ctx->hidden.androidio.position; 1614 if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size; 1615 offset += ctx->hidden.androidio.offset; 1616 break; 1617 case RW_SEEK_END: 1618 offset = ctx->hidden.androidio.offset + ctx->hidden.androidio.size + offset; 1619 break; 1620 default: 1621 return SDL_SetError("Unknown value for 'whence'"); 1622 } 1623 whence = SEEK_SET; 1624 1625 ret = lseek(ctx->hidden.androidio.fd, (off_t)offset, SEEK_SET); 1626 if (ret == -1) return -1; 1627 ctx->hidden.androidio.position = ret - ctx->hidden.androidio.offset; 1628 } else { 1629 Sint64 newPosition; 1630 Sint64 movement; 1631 1632 switch (whence) { 1633 case RW_SEEK_SET: 1634 newPosition = offset; 1635 break; 1636 case RW_SEEK_CUR: 1637 newPosition = ctx->hidden.androidio.position + offset; 1638 break; 1639 case RW_SEEK_END: 1640 newPosition = ctx->hidden.androidio.size + offset; 1641 break; 1642 default: 1643 return SDL_SetError("Unknown value for 'whence'"); 1644 } 1645 1646 /* Validate the new position */ 1647 if (newPosition < 0) { 1648 return SDL_Error(SDL_EFSEEK); 1649 } 1650 if (newPosition > ctx->hidden.androidio.size) { 1651 newPosition = ctx->hidden.androidio.size; 1652 } 1653 1654 movement = newPosition - ctx->hidden.androidio.position; 1655 if (movement > 0) { 1656 unsigned char buffer[4096]; 1657 1658 /* The easy case where we're seeking forwards */ 1659 while (movement > 0) { 1660 Sint64 amount = sizeof (buffer); 1661 size_t result; 1662 if (amount > movement) { 1663 amount = movement; 1664 } 1665 result = Android_JNI_FileRead(ctx, buffer, 1, amount); 1666 if (result <= 0) { 1667 /* Failed to read/skip the required amount, so fail */ 1668 return -1; 1669 } 1670 1671 movement -= result; 1672 } 1673 1674 } else if (movement < 0) { 1675 /* We can't seek backwards so we have to reopen the file and seek */ 1676 /* forwards which obviously isn't very efficient */ 1677 Internal_Android_JNI_FileClose(ctx, SDL_FALSE); 1678 Internal_Android_JNI_FileOpen(ctx); 1679 Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET); 1680 } 1681 } 1682 1683 return ctx->hidden.androidio.position; 1684 1685} 1686 1687int Android_JNI_FileClose(SDL_RWops* ctx) 1688{ 1689 return Internal_Android_JNI_FileClose(ctx, SDL_TRUE); 1690} 1691 1692int Android_JNI_SetClipboardText(const char* text) 1693{ 1694 JNIEnv* env = Android_JNI_GetEnv(); 1695 jstring string = (*env)->NewStringUTF(env, text); 1696 (*env)->CallStaticVoidMethod(env, mActivityClass, midClipboardSetText, string); 1697 (*env)->DeleteLocalRef(env, string); 1698 return 0; 1699} 1700 1701char* Android_JNI_GetClipboardText(void) 1702{ 1703 JNIEnv* env = Android_JNI_GetEnv(); 1704 char* text = NULL; 1705 jstring string; 1706 1707 string = (*env)->CallStaticObjectMethod(env, mActivityClass, midClipboardGetText); 1708 if (string) { 1709 const char* utf = (*env)->GetStringUTFChars(env, string, 0); 1710 if (utf) { 1711 text = SDL_strdup(utf); 1712 (*env)->ReleaseStringUTFChars(env, string, utf); 1713 } 1714 (*env)->DeleteLocalRef(env, string); 1715 } 1716 1717 return (text == NULL) ? SDL_strdup("") : text; 1718} 1719 1720SDL_bool Android_JNI_HasClipboardText(void) 1721{ 1722 JNIEnv* env = Android_JNI_GetEnv(); 1723 jboolean retval = (*env)->CallStaticBooleanMethod(env, mActivityClass, midClipboardHasText); 1724 return (retval == JNI_TRUE) ? SDL_TRUE : SDL_FALSE; 1725} 1726 1727/* returns 0 on success or -1 on error (others undefined then) 1728 * returns truthy or falsy value in plugged, charged and battery 1729 * returns the value in seconds and percent or -1 if not available 1730 */ 1731int Android_JNI_GetPowerInfo(int* plugged, int* charged, int* battery, int* seconds, int* percent) 1732{ 1733 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); 1734 JNIEnv* env = Android_JNI_GetEnv(); 1735 jmethodID mid; 1736 jobject context; 1737 jstring action; 1738 jclass cls; 1739 jobject filter; 1740 jobject intent; 1741 jstring iname; 1742 jmethodID imid; 1743 jstring bname; 1744 jmethodID bmid; 1745 if (!LocalReferenceHolder_Init(&refs, env)) { 1746 LocalReferenceHolder_Cleanup(&refs); 1747 return -1; 1748 } 1749 1750 1751 /* context = SDLActivity.getContext(); */ 1752 context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext); 1753 1754 action = (*env)->NewStringUTF(env, "android.intent.action.BATTERY_CHANGED"); 1755 1756 cls = (*env)->FindClass(env, "android/content/IntentFilter"); 1757 1758 mid = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/lang/String;)V"); 1759 filter = (*env)->NewObject(env, cls, mid, action); 1760 1761 (*env)->DeleteLocalRef(env, action); 1762 1763 mid = (*env)->GetMethodID(env, mActivityClass, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;"); 1764 intent = (*env)->CallObjectMethod(env, context, mid, NULL, filter); 1765 1766 (*env)->DeleteLocalRef(env, filter); 1767 1768 cls = (*env)->GetObjectClass(env, intent); 1769 1770 imid = (*env)->GetMethodID(env, cls, "getIntExtra", "(Ljava/lang/String;I)I"); 1771 1772 /* Watch out for C89 scoping rules because of the macro */ 1773#define GET_INT_EXTRA(var, key) \ 1774 int var; \ 1775 iname = (*env)->NewStringUTF(env, key); \ 1776 var = (*env)->CallIntMethod(env, intent, imid, iname, -1); \ 1777 (*env)->DeleteLocalRef(env, iname); 1778 1779 bmid = (*env)->GetMethodID(env, cls, "getBooleanExtra", "(Ljava/lang/String;Z)Z"); 1780 1781 /* Watch out for C89 scoping rules because of the macro */ 1782#define GET_BOOL_EXTRA(var, key) \ 1783 int var; \ 1784 bname = (*env)->NewStringUTF(env, key); \ 1785 var = (*env)->CallBooleanMethod(env, intent, bmid, bname, JNI_FALSE); \ 1786 (*env)->DeleteLocalRef(env, bname); 1787 1788 if (plugged) { 1789 /* Watch out for C89 scoping rules because of the macro */ 1790 GET_INT_EXTRA(plug, "plugged") /* == BatteryManager.EXTRA_PLUGGED (API 5) */ 1791 if (plug == -1) { 1792 LocalReferenceHolder_Cleanup(&refs); 1793 return -1; 1794 } 1795 /* 1 == BatteryManager.BATTERY_PLUGGED_AC */ 1796 /* 2 == BatteryManager.BATTERY_PLUGGED_USB */ 1797 *plugged = (0 < plug) ? 1 : 0; 1798 } 1799 1800 if (charged) { 1801 /* Watch out for C89 scoping rules because of the macro */ 1802 GET_INT_EXTRA(status, "status") /* == BatteryManager.EXTRA_STATUS (API 5) */ 1803 if (status == -1) { 1804 LocalReferenceHolder_Cleanup(&refs); 1805 return -1; 1806 } 1807 /* 5 == BatteryManager.BATTERY_STATUS_FULL */ 1808 *charged = (status == 5) ? 1 : 0; 1809 } 1810 1811 if (battery) { 1812 GET_BOOL_EXTRA(present, "present") /* == BatteryManager.EXTRA_PRESENT (API 5) */ 1813 *battery = present ? 1 : 0; 1814 } 1815 1816 if (seconds) { 1817 *seconds = -1; /* not possible */ 1818 } 1819 1820 if (percent) { 1821 int level; 1822 int scale; 1823 1824 /* Watch out for C89 scoping rules because of the macro */ 1825 { 1826 GET_INT_EXTRA(level_temp, "level") /* == BatteryManager.EXTRA_LEVEL (API 5) */ 1827 level = level_temp; 1828 } 1829 /* Watch out for C89 scoping rules because of the macro */ 1830 { 1831 GET_INT_EXTRA(scale_temp, "scale") /* == BatteryManager.EXTRA_SCALE (API 5) */ 1832 scale = scale_temp; 1833 } 1834 1835 if ((level == -1) || (scale == -1)) { 1836 LocalReferenceHolder_Cleanup(&refs); 1837 return -1; 1838 } 1839 *percent = level * 100 / scale; 1840 } 1841 1842 (*env)->DeleteLocalRef(env, intent); 1843 1844 LocalReferenceHolder_Cleanup(&refs); 1845 return 0; 1846} 1847 1848/* returns number of found touch devices as return value and ids in parameter ids */ 1849int Android_JNI_GetTouchDeviceIds(int **ids) { 1850 JNIEnv *env = Android_JNI_GetEnv(); 1851 jint sources = 4098; /* == InputDevice.SOURCE_TOUCHSCREEN */ 1852 jintArray array = (jintArray) (*env)->CallStaticObjectMethod(env, mActivityClass, midInputGetInputDeviceIds, sources); 1853 int number = 0; 1854 *ids = NULL; 1855 if (array) { 1856 number = (int) (*env)->GetArrayLength(env, array); 1857 if (0 < number) { 1858 jint* elements = (*env)->GetIntArrayElements(env, array, NULL); 1859 if (elements) { 1860 int i; 1861 *ids = SDL_malloc(number * sizeof (**ids)); 1862 for (i = 0; i < number; ++i) { /* not assuming sizeof (jint) == sizeof (int) */ 1863 (*ids)[i] = elements[i]; 1864 } 1865 (*env)->ReleaseIntArrayElements(env, array, elements, JNI_ABORT); 1866 } 1867 } 1868 (*env)->DeleteLocalRef(env, array); 1869 } 1870 return number; 1871} 1872 1873/* sets the mSeparateMouseAndTouch field */ 1874void Android_JNI_SetSeparateMouseAndTouch(SDL_bool new_value) 1875{ 1876 JNIEnv *env = Android_JNI_GetEnv(); 1877 (*env)->SetStaticBooleanField(env, mActivityClass, fidSeparateMouseAndTouch, new_value ? JNI_TRUE : JNI_FALSE); 1878} 1879 1880void Android_JNI_PollInputDevices(void) 1881{ 1882 JNIEnv *env = Android_JNI_GetEnv(); 1883 (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midPollInputDevices); 1884} 1885 1886void Android_JNI_PollHapticDevices(void) 1887{ 1888 JNIEnv *env = Android_JNI_GetEnv(); 1889 (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midPollHapticDevices); 1890} 1891 1892void Android_JNI_HapticRun(int device_id, int length) 1893{ 1894 JNIEnv *env = Android_JNI_GetEnv(); 1895 (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midHapticRun, device_id, length); 1896} 1897 1898void Android_JNI_HapticStop(int device_id) 1899{ 1900 JNIEnv *env = Android_JNI_GetEnv(); 1901 (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midHapticStop, device_id); 1902} 1903 1904/* See SDLActivity.java for constants. */ 1905#define COMMAND_SET_KEEP_SCREEN_ON 5 1906 1907/* sends message to be handled on the UI event dispatch thread */ 1908int Android_JNI_SendMessage(int command, int param) 1909{ 1910 JNIEnv *env = Android_JNI_GetEnv(); 1911 jboolean success; 1912 success = (*env)->CallStaticBooleanMethod(env, mActivityClass, midSendMessage, command, param); 1913 return success ? 0 : -1; 1914} 1915 1916void Android_JNI_SuspendScreenSaver(SDL_bool suspend) 1917{ 1918 Android_JNI_SendMessage(COMMAND_SET_KEEP_SCREEN_ON, (suspend == SDL_FALSE) ? 0 : 1); 1919} 1920 1921void Android_JNI_ShowTextInput(SDL_Rect *inputRect) 1922{ 1923 JNIEnv *env = Android_JNI_GetEnv(); 1924 (*env)->CallStaticBooleanMethod(env, mActivityClass, midShowTextInput, 1925 inputRect->x, 1926 inputRect->y, 1927 inputRect->w, 1928 inputRect->h ); 1929} 1930 1931void Android_JNI_HideTextInput(void) 1932{ 1933 /* has to match Activity constant */ 1934 const int COMMAND_TEXTEDIT_HIDE = 3; 1935 Android_JNI_SendMessage(COMMAND_TEXTEDIT_HIDE, 0); 1936} 1937 1938SDL_bool Android_JNI_IsScreenKeyboardShown() 1939{ 1940 JNIEnv *mEnv = Android_JNI_GetEnv(); 1941 jboolean is_shown = 0; 1942 is_shown = (*mEnv)->CallStaticBooleanMethod(mEnv, mActivityClass, midIsScreenKeyboardShown); 1943 return is_shown; 1944} 1945 1946 1947int Android_JNI_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid) 1948{ 1949 JNIEnv *env; 1950 jclass clazz; 1951 jmethodID mid; 1952 jobject context; 1953 jstring title; 1954 jstring message; 1955 jintArray button_flags; 1956 jintArray button_ids; 1957 jobjectArray button_texts; 1958 jintArray colors; 1959 jobject text; 1960 jint temp; 1961 int i; 1962 1963 env = Android_JNI_GetEnv(); 1964 1965 /* convert parameters */ 1966 1967 clazz = (*env)->FindClass(env, "java/lang/String"); 1968 1969 title = (*env)->NewStringUTF(env, messageboxdata->title); 1970 message = (*env)->NewStringUTF(env, messageboxdata->message); 1971 1972 button_flags = (*env)->NewIntArray(env, messageboxdata->numbuttons); 1973 button_ids = (*env)->NewIntArray(env, messageboxdata->numbuttons); 1974 button_texts = (*env)->NewObjectArray(env, messageboxdata->numbuttons, 1975 clazz, NULL); 1976 for (i = 0; i < messageboxdata->numbuttons; ++i) { 1977 temp = messageboxdata->buttons[i].flags; 1978 (*env)->SetIntArrayRegion(env, button_flags, i, 1, &temp); 1979 temp = messageboxdata->buttons[i].buttonid; 1980 (*env)->SetIntArrayRegion(env, button_ids, i, 1, &temp); 1981 text = (*env)->NewStringUTF(env, messageboxdata->buttons[i].text); 1982 (*env)->SetObjectArrayElement(env, button_texts, i, text); 1983 (*env)->DeleteLocalRef(env, text); 1984 } 1985 1986 if (messageboxdata->colorScheme) { 1987 colors = (*env)->NewIntArray(env, SDL_MESSAGEBOX_COLOR_MAX); 1988 for (i = 0; i < SDL_MESSAGEBOX_COLOR_MAX; ++i) { 1989 temp = (0xFF << 24) | 1990 (messageboxdata->colorScheme->colors[i].r << 16) | 1991 (messageboxdata->colorScheme->colors[i].g << 8) | 1992 (messageboxdata->colorScheme->colors[i].b << 0); 1993 (*env)->SetIntArrayRegion(env, colors, i, 1, &temp); 1994 } 1995 } else { 1996 colors = NULL; 1997 } 1998 1999 (*env)->DeleteLocalRef(env, clazz); 2000 2001 /* context = SDLActivity.getContext(); */ 2002 context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext); 2003 2004 clazz = (*env)->GetObjectClass(env, context); 2005 2006 mid = (*env)->GetMethodID(env, clazz, 2007 "messageboxShowMessageBox", "(ILjava/lang/String;Ljava/lang/String;[I[I[Ljava/lang/String;[I)I"); 2008 *buttonid = (*env)->CallIntMethod(env, context, mid, 2009 messageboxdata->flags, 2010 title, 2011 message, 2012 button_flags, 2013 button_ids, 2014 button_texts, 2015 colors); 2016 2017 (*env)->DeleteLocalRef(env, context); 2018 (*env)->DeleteLocalRef(env, clazz); 2019 2020 /* delete parameters */ 2021 2022 (*env)->DeleteLocalRef(env, title); 2023 (*env)->DeleteLocalRef(env, message); 2024 (*env)->DeleteLocalRef(env, button_flags); 2025 (*env)->DeleteLocalRef(env, button_ids); 2026 (*env)->DeleteLocalRef(env, button_texts); 2027 (*env)->DeleteLocalRef(env, colors); 2028 2029 return 0; 2030} 2031 2032/* 2033////////////////////////////////////////////////////////////////////////////// 2034// 2035// Functions exposed to SDL applications in SDL_system.h 2036////////////////////////////////////////////////////////////////////////////// 2037*/ 2038 2039void *SDL_AndroidGetJNIEnv(void) 2040{ 2041 return Android_JNI_GetEnv(); 2042} 2043 2044void *SDL_AndroidGetActivity(void) 2045{ 2046 /* See SDL_system.h for caveats on using this function. */ 2047 2048 JNIEnv *env = Android_JNI_GetEnv(); 2049 if (!env) { 2050 return NULL; 2051 } 2052 2053 /* return SDLActivity.getContext(); */ 2054 return (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext); 2055} 2056 2057SDL_bool SDL_IsAndroidTablet(void) 2058{ 2059 JNIEnv *env = Android_JNI_GetEnv(); 2060 return (*env)->CallStaticBooleanMethod(env, mActivityClass, midIsTablet); 2061} 2062 2063SDL_bool SDL_IsAndroidTV(void) 2064{ 2065 JNIEnv *env = Android_JNI_GetEnv(); 2066 return (*env)->CallStaticBooleanMethod(env, mActivityClass, midIsAndroidTV); 2067} 2068 2069SDL_bool SDL_IsChromebook(void) 2070{ 2071 JNIEnv *env = Android_JNI_GetEnv(); 2072 return (*env)->CallStaticBooleanMethod(env, mActivityClass, midIsChromebook); 2073} 2074 2075SDL_bool SDL_IsDeXMode(void) 2076{ 2077 JNIEnv *env = Android_JNI_GetEnv(); 2078 return (*env)->CallStaticBooleanMethod(env, mActivityClass, midIsDeXMode); 2079} 2080 2081void SDL_AndroidBackButton(void) 2082{ 2083 JNIEnv *env = Android_JNI_GetEnv(); 2084 return (*env)->CallStaticVoidMethod(env, mActivityClass, midManualBackButton); 2085} 2086 2087const char * SDL_AndroidGetInternalStoragePath(void) 2088{ 2089 static char *s_AndroidInternalFilesPath = NULL; 2090 2091 if (!s_AndroidInternalFilesPath) { 2092 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); 2093 jmethodID mid; 2094 jobject context; 2095 jobject fileObject; 2096 jstring pathString; 2097 const char *path; 2098 2099 JNIEnv *env = Android_JNI_GetEnv(); 2100 if (!LocalReferenceHolder_Init(&refs, env)) { 2101 LocalReferenceHolder_Cleanup(&refs); 2102 return NULL; 2103 } 2104 2105 /* context = SDLActivity.getContext(); */ 2106 context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext); 2107 if (!context) { 2108 SDL_SetError("Couldn't get Android context!"); 2109 LocalReferenceHolder_Cleanup(&refs); 2110 return NULL; 2111 } 2112 2113 /* fileObj = context.getFilesDir(); */ 2114 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context), 2115 "getFilesDir", "()Ljava/io/File;"); 2116 fileObject = (*env)->CallObjectMethod(env, context, mid); 2117 if (!fileObject) { 2118 SDL_SetError("Couldn't get internal directory"); 2119 LocalReferenceHolder_Cleanup(&refs); 2120 return NULL; 2121 } 2122 2123 /* path = fileObject.getCanonicalPath(); */ 2124 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject), 2125 "getCanonicalPath", "()Ljava/lang/String;"); 2126 pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid); 2127 if (Android_JNI_ExceptionOccurred(SDL_FALSE)) { 2128 LocalReferenceHolder_Cleanup(&refs); 2129 return NULL; 2130 } 2131 2132 path = (*env)->GetStringUTFChars(env, pathString, NULL); 2133 s_AndroidInternalFilesPath = SDL_strdup(path); 2134 (*env)->ReleaseStringUTFChars(env, pathString, path); 2135 2136 LocalReferenceHolder_Cleanup(&refs); 2137 } 2138 return s_AndroidInternalFilesPath; 2139} 2140 2141int SDL_AndroidGetExternalStorageState(void) 2142{ 2143 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); 2144 jmethodID mid; 2145 jclass cls; 2146 jstring stateString; 2147 const char *state; 2148 int stateFlags; 2149 2150 JNIEnv *env = Android_JNI_GetEnv(); 2151 if (!LocalReferenceHolder_Init(&refs, env)) { 2152 LocalReferenceHolder_Cleanup(&refs); 2153 return 0; 2154 } 2155 2156 cls = (*env)->FindClass(env, "android/os/Environment"); 2157 mid = (*env)->GetStaticMethodID(env, cls, 2158 "getExternalStorageState", "()Ljava/lang/String;"); 2159 stateString = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid); 2160 2161 state = (*env)->GetStringUTFChars(env, stateString, NULL); 2162 2163 /* Print an info message so people debugging know the storage state */ 2164 __android_log_print(ANDROID_LOG_INFO, "SDL", "external storage state: %s", state); 2165 2166 if (SDL_strcmp(state, "mounted") == 0) { 2167 stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ | 2168 SDL_ANDROID_EXTERNAL_STORAGE_WRITE; 2169 } else if (SDL_strcmp(state, "mounted_ro") == 0) { 2170 stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ; 2171 } else { 2172 stateFlags = 0; 2173 } 2174 (*env)->ReleaseStringUTFChars(env, stateString, state); 2175 2176 LocalReferenceHolder_Cleanup(&refs); 2177 return stateFlags; 2178} 2179 2180const char * SDL_AndroidGetExternalStoragePath(void) 2181{ 2182 static char *s_AndroidExternalFilesPath = NULL; 2183 2184 if (!s_AndroidExternalFilesPath) { 2185 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); 2186 jmethodID mid; 2187 jobject context; 2188 jobject fileObject; 2189 jstring pathString; 2190 const char *path; 2191 2192 JNIEnv *env = Android_JNI_GetEnv(); 2193 if (!LocalReferenceHolder_Init(&refs, env)) { 2194 LocalReferenceHolder_Cleanup(&refs); 2195 return NULL; 2196 } 2197 2198 /* context = SDLActivity.getContext(); */ 2199 context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext); 2200 2201 /* fileObj = context.getExternalFilesDir(); */ 2202 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context), 2203 "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;"); 2204 fileObject = (*env)->CallObjectMethod(env, context, mid, NULL); 2205 if (!fileObject) { 2206 SDL_SetError("Couldn't get external directory"); 2207 LocalReferenceHolder_Cleanup(&refs); 2208 return NULL; 2209 } 2210 2211 /* path = fileObject.getAbsolutePath(); */ 2212 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject), 2213 "getAbsolutePath", "()Ljava/lang/String;"); 2214 pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid); 2215 2216 path = (*env)->GetStringUTFChars(env, pathString, NULL); 2217 s_AndroidExternalFilesPath = SDL_strdup(path); 2218 (*env)->ReleaseStringUTFChars(env, pathString, path); 2219 2220 LocalReferenceHolder_Cleanup(&refs); 2221 } 2222 return s_AndroidExternalFilesPath; 2223} 2224 2225void Android_JNI_GetManifestEnvironmentVariables(void) 2226{ 2227 if (!mActivityClass || !midGetManifestEnvironmentVariables) { 2228 __android_log_print(ANDROID_LOG_WARN, "SDL", "Request to get environment variables before JNI is ready"); 2229 return; 2230 } 2231 2232 if (!bHasEnvironmentVariables) { 2233 JNIEnv *env = Android_JNI_GetEnv(); 2234 SDL_bool ret = (*env)->CallStaticBooleanMethod(env, mActivityClass, midGetManifestEnvironmentVariables); 2235 if (ret) { 2236 bHasEnvironmentVariables = SDL_TRUE; 2237 } 2238 } 2239} 2240 2241int Android_JNI_CreateCustomCursor(SDL_Surface *surface, int hot_x, int hot_y) 2242{ 2243 JNIEnv *mEnv = Android_JNI_GetEnv(); 2244 int custom_cursor = 0; 2245 jintArray pixels; 2246 pixels = (*mEnv)->NewIntArray(mEnv, surface->w * surface->h); 2247 if (pixels) { 2248 (*mEnv)->SetIntArrayRegion(mEnv, pixels, 0, surface->w * surface->h, (int *)surface->pixels); 2249 custom_cursor = (*mEnv)->CallStaticIntMethod(mEnv, mActivityClass, midCreateCustomCursor, pixels, surface->w, surface->h, hot_x, hot_y); 2250 (*mEnv)->DeleteLocalRef(mEnv, pixels); 2251 } else { 2252 SDL_OutOfMemory(); 2253 } 2254 return custom_cursor; 2255} 2256 2257 2258SDL_bool Android_JNI_SetCustomCursor(int cursorID) 2259{ 2260 JNIEnv *mEnv = Android_JNI_GetEnv(); 2261 return (*mEnv)->CallStaticBooleanMethod(mEnv, mActivityClass, midSetCustomCursor, cursorID); 2262} 2263 2264SDL_bool Android_JNI_SetSystemCursor(int cursorID) 2265{ 2266 JNIEnv *mEnv = Android_JNI_GetEnv(); 2267 return (*mEnv)->CallStaticBooleanMethod(mEnv, mActivityClass, midSetSystemCursor, cursorID); 2268} 2269 2270SDL_bool Android_JNI_SupportsRelativeMouse() 2271{ 2272 JNIEnv *mEnv = Android_JNI_GetEnv(); 2273 return (*mEnv)->CallStaticBooleanMethod(mEnv, mActivityClass, midSupportsRelativeMouse); 2274} 2275 2276SDL_bool Android_JNI_SetRelativeMouseEnabled(SDL_bool enabled) 2277{ 2278 JNIEnv *mEnv = Android_JNI_GetEnv(); 2279 return (*mEnv)->CallStaticBooleanMethod(mEnv, mActivityClass, midSetRelativeMouseEnabled, (enabled == 1)); 2280} 2281 2282 2283#endif /* __ANDROID__ */ 2284 2285/* vi: set ts=4 sw=4 expandtab: */ 2286
[FILE END]
(C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.