SDL_android.c (57668B)
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2014 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#include "SDL_stdinc.h" 23#include "SDL_assert.h" 24#include "SDL_hints.h" 25#include "SDL_log.h" 26 27#ifdef __ANDROID__ 28 29#include "SDL_system.h" 30#include "SDL_android.h" 31#include <EGL/egl.h> 32 33#include "../../events/SDL_events_c.h" 34#include "../../video/android/SDL_androidkeyboard.h" 35#include "../../video/android/SDL_androidtouch.h" 36#include "../../video/android/SDL_androidvideo.h" 37#include "../../video/android/SDL_androidwindow.h" 38#include "../../joystick/android/SDL_sysjoystick_c.h" 39 40#include <android/log.h> 41#include <pthread.h> 42#include <sys/types.h> 43#include <unistd.h> 44#define LOG_TAG "SDL_android" 45/* #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) */ 46/* #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) */ 47#define LOGI(...) do {} while (false) 48#define LOGE(...) do {} while (false) 49 50/* Uncomment this to log messages entering and exiting methods in this file */ 51/* #define DEBUG_JNI */ 52 53static void Android_JNI_ThreadDestroyed(void*); 54 55/******************************************************************************* 56 This file links the Java side of Android with libsdl 57*******************************************************************************/ 58#include <jni.h> 59#include <android/log.h> 60#include <stdbool.h> 61 62 63/******************************************************************************* 64 Globals 65*******************************************************************************/ 66static pthread_key_t mThreadKey; 67static JavaVM* mJavaVM; 68 69/* Main activity */ 70static jclass mActivityClass; 71 72/* method signatures */ 73static jmethodID midGetNativeSurface; 74static jmethodID midFlipBuffers; 75static jmethodID midAudioInit; 76static jmethodID midAudioWriteShortBuffer; 77static jmethodID midAudioWriteByteBuffer; 78static jmethodID midAudioQuit; 79static jmethodID midPollInputDevices; 80 81/* Accelerometer data storage */ 82static float fLastAccelerometer[3]; 83static bool bHasNewData; 84 85/******************************************************************************* 86 Functions called by JNI 87*******************************************************************************/ 88 89/* Library init */ 90JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) 91{ 92 JNIEnv *env; 93 mJavaVM = vm; 94 LOGI("JNI_OnLoad called"); 95 if ((*mJavaVM)->GetEnv(mJavaVM, (void**) &env, JNI_VERSION_1_4) != JNI_OK) { 96 LOGE("Failed to get the environment using GetEnv()"); 97 return -1; 98 } 99 /* 100 * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread 101 * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this 102 */ 103 if (pthread_key_create(&mThreadKey, Android_JNI_ThreadDestroyed) != 0) { 104 __android_log_print(ANDROID_LOG_ERROR, "SDL", "Error initializing pthread key"); 105 } 106 Android_JNI_SetupThread(); 107 108 return JNI_VERSION_1_4; 109} 110 111/* Called before SDL_main() to initialize JNI bindings */ 112JNIEXPORT void JNICALL SDL_Android_Init(JNIEnv* mEnv, jclass cls) 113{ 114 __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init()"); 115 116 Android_JNI_SetupThread(); 117 118 mActivityClass = (jclass)((*mEnv)->NewGlobalRef(mEnv, cls)); 119 120 midGetNativeSurface = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, 121 "getNativeSurface","()Landroid/view/Surface;"); 122 midFlipBuffers = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, 123 "flipBuffers","()V"); 124 midAudioInit = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, 125 "audioInit", "(IZZI)I"); 126 midAudioWriteShortBuffer = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, 127 "audioWriteShortBuffer", "([S)V"); 128 midAudioWriteByteBuffer = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, 129 "audioWriteByteBuffer", "([B)V"); 130 midAudioQuit = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, 131 "audioQuit", "()V"); 132 midPollInputDevices = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, 133 "pollInputDevices", "()V"); 134 135 bHasNewData = false; 136 137 if(!midGetNativeSurface || !midFlipBuffers || !midAudioInit || 138 !midAudioWriteShortBuffer || !midAudioWriteByteBuffer || !midAudioQuit || !midPollInputDevices) { 139 __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL: Couldn't locate Java callbacks, check that they're named and typed correctly"); 140 } 141 __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init() finished!"); 142} 143 144/* Resize */ 145JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_onNativeResize( 146 JNIEnv* env, jclass jcls, 147 jint width, jint height, jint format) 148{ 149 Android_SetScreenResolution(width, height, format); 150} 151 152/* Paddown */ 153JNIEXPORT int JNICALL Java_org_libsdl_app_SDLActivity_onNativePadDown( 154 JNIEnv* env, jclass jcls, 155 jint device_id, jint keycode) 156{ 157 return Android_OnPadDown(device_id, keycode); 158} 159 160/* Padup */ 161JNIEXPORT int JNICALL Java_org_libsdl_app_SDLActivity_onNativePadUp( 162 JNIEnv* env, jclass jcls, 163 jint device_id, jint keycode) 164{ 165 return Android_OnPadUp(device_id, keycode); 166} 167 168/* Joy */ 169JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_onNativeJoy( 170 JNIEnv* env, jclass jcls, 171 jint device_id, jint axis, jfloat value) 172{ 173 Android_OnJoy(device_id, axis, value); 174} 175 176/* POV Hat */ 177JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_onNativeHat( 178 JNIEnv* env, jclass jcls, 179 jint device_id, jint hat_id, jint x, jint y) 180{ 181 Android_OnHat(device_id, hat_id, x, y); 182} 183 184 185JNIEXPORT int JNICALL Java_org_libsdl_app_SDLActivity_nativeAddJoystick( 186 JNIEnv* env, jclass jcls, 187 jint device_id, jstring device_name, jint is_accelerometer, 188 jint nbuttons, jint naxes, jint nhats, jint nballs) 189{ 190 int retval; 191 const char *name = (*env)->GetStringUTFChars(env, device_name, NULL); 192 193 retval = Android_AddJoystick(device_id, name, (SDL_bool) is_accelerometer, nbuttons, naxes, nhats, nballs); 194 195 (*env)->ReleaseStringUTFChars(env, device_name, name); 196 197 return retval; 198} 199 200JNIEXPORT int JNICALL Java_org_libsdl_app_SDLActivity_nativeRemoveJoystick( 201 JNIEnv* env, jclass jcls, jint device_id) 202{ 203 return Android_RemoveJoystick(device_id); 204} 205 206 207/* Surface Created */ 208JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_onNativeSurfaceChanged(JNIEnv* env, jclass jcls) 209{ 210 SDL_WindowData *data; 211 SDL_VideoDevice *_this; 212 213 if (Android_Window == NULL || Android_Window->driverdata == NULL ) { 214 return; 215 } 216 217 _this = SDL_GetVideoDevice(); 218 data = (SDL_WindowData *) Android_Window->driverdata; 219 220 /* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */ 221 if (data->egl_surface == EGL_NO_SURFACE) { 222 if(data->native_window) { 223 ANativeWindow_release(data->native_window); 224 } 225 data->native_window = Android_JNI_GetNativeWindow(); 226 data->egl_surface = SDL_EGL_CreateSurface(_this, (NativeWindowType) data->native_window); 227 } 228 229 /* GL Context handling is done in the event loop because this function is run from the Java thread */ 230 231} 232 233/* Surface Destroyed */ 234JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_onNativeSurfaceDestroyed(JNIEnv* env, jclass jcls) 235{ 236 /* We have to clear the current context and destroy the egl surface here 237 * Otherwise there's BAD_NATIVE_WINDOW errors coming from eglCreateWindowSurface on resume 238 * Ref: http://stackoverflow.com/questions/8762589/eglcreatewindowsurface-on-ics-and-switching-from-2d-to-3d 239 */ 240 SDL_WindowData *data; 241 SDL_VideoDevice *_this; 242 243 if (Android_Window == NULL || Android_Window->driverdata == NULL ) { 244 return; 245 } 246 247 _this = SDL_GetVideoDevice(); 248 data = (SDL_WindowData *) Android_Window->driverdata; 249 250 if (data->egl_surface != EGL_NO_SURFACE) { 251 SDL_EGL_MakeCurrent(_this, NULL, NULL); 252 SDL_EGL_DestroySurface(_this, data->egl_surface); 253 data->egl_surface = EGL_NO_SURFACE; 254 } 255 256 /* GL Context handling is done in the event loop because this function is run from the Java thread */ 257 258} 259 260JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_nativeFlipBuffers(JNIEnv* env, jclass jcls) 261{ 262 SDL_GL_SwapWindow(Android_Window); 263} 264 265/* Keydown */ 266JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_onNativeKeyDown( 267 JNIEnv* env, jclass jcls, jint keycode) 268{ 269 Android_OnKeyDown(keycode); 270} 271 272/* Keyup */ 273JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_onNativeKeyUp( 274 JNIEnv* env, jclass jcls, jint keycode) 275{ 276 Android_OnKeyUp(keycode); 277} 278 279/* Keyboard Focus Lost */ 280JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_onNativeKeyboardFocusLost( 281 JNIEnv* env, jclass jcls) 282{ 283 /* Calling SDL_StopTextInput will take care of hiding the keyboard and cleaning up the DummyText widget */ 284 SDL_StopTextInput(); 285} 286 287 288/* Touch */ 289JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_onNativeTouch( 290 JNIEnv* env, jclass jcls, 291 jint touch_device_id_in, jint pointer_finger_id_in, 292 jint action, jfloat x, jfloat y, jfloat p) 293{ 294 Android_OnTouch(touch_device_id_in, pointer_finger_id_in, action, x, y, p); 295} 296 297/* Accelerometer */ 298JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_onNativeAccel( 299 JNIEnv* env, jclass jcls, 300 jfloat x, jfloat y, jfloat z) 301{ 302 fLastAccelerometer[0] = x; 303 fLastAccelerometer[1] = y; 304 fLastAccelerometer[2] = z; 305 bHasNewData = true; 306} 307 308/* Low memory */ 309JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_nativeLowMemory( 310 JNIEnv* env, jclass cls) 311{ 312 SDL_SendAppEvent(SDL_APP_LOWMEMORY); 313} 314 315/* Quit */ 316JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_nativeQuit( 317 JNIEnv* env, jclass cls) 318{ 319 /* Discard previous events. The user should have handled state storage 320 * in SDL_APP_WILLENTERBACKGROUND. After nativeQuit() is called, no 321 * events other than SDL_QUIT and SDL_APP_TERMINATING should fire */ 322 SDL_FlushEvents(SDL_FIRSTEVENT, SDL_LASTEVENT); 323 /* Inject a SDL_QUIT event */ 324 SDL_SendQuit(); 325 SDL_SendAppEvent(SDL_APP_TERMINATING); 326 /* Resume the event loop so that the app can catch SDL_QUIT which 327 * should now be the top event in the event queue. */ 328 if (!SDL_SemValue(Android_ResumeSem)) SDL_SemPost(Android_ResumeSem); 329} 330 331/* Pause */ 332JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_nativePause( 333 JNIEnv* env, jclass cls) 334{ 335 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativePause()"); 336 if (Android_Window) { 337 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_LOST, 0, 0); 338 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_MINIMIZED, 0, 0); 339 SDL_SendAppEvent(SDL_APP_WILLENTERBACKGROUND); 340 SDL_SendAppEvent(SDL_APP_DIDENTERBACKGROUND); 341 342 /* *After* sending the relevant events, signal the pause semaphore 343 * so the event loop knows to pause and (optionally) block itself */ 344 if (!SDL_SemValue(Android_PauseSem)) SDL_SemPost(Android_PauseSem); 345 } 346} 347 348/* Resume */ 349JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_nativeResume( 350 JNIEnv* env, jclass cls) 351{ 352 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeResume()"); 353 354 if (Android_Window) { 355 SDL_SendAppEvent(SDL_APP_WILLENTERFOREGROUND); 356 SDL_SendAppEvent(SDL_APP_DIDENTERFOREGROUND); 357 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_GAINED, 0, 0); 358 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_RESTORED, 0, 0); 359 /* Signal the resume semaphore so the event loop knows to resume and restore the GL Context 360 * We can't restore the GL Context here because it needs to be done on the SDL main thread 361 * and this function will be called from the Java thread instead. 362 */ 363 if (!SDL_SemValue(Android_ResumeSem)) SDL_SemPost(Android_ResumeSem); 364 } 365} 366 367JNIEXPORT void JNICALL Java_org_libsdl_app_SDLInputConnection_nativeCommitText( 368 JNIEnv* env, jclass cls, 369 jstring text, jint newCursorPosition) 370{ 371 const char *utftext = (*env)->GetStringUTFChars(env, text, NULL); 372 373 SDL_SendKeyboardText(utftext); 374 375 (*env)->ReleaseStringUTFChars(env, text, utftext); 376} 377 378JNIEXPORT void JNICALL Java_org_libsdl_app_SDLInputConnection_nativeSetComposingText( 379 JNIEnv* env, jclass cls, 380 jstring text, jint newCursorPosition) 381{ 382 const char *utftext = (*env)->GetStringUTFChars(env, text, NULL); 383 384 SDL_SendEditingText(utftext, 0, 0); 385 386 (*env)->ReleaseStringUTFChars(env, text, utftext); 387} 388 389jstring Java_org_libsdl_app_SDLActivity_nativeGetHint(JNIEnv* env, jclass cls, jstring name) { 390 const char *utfname = (*env)->GetStringUTFChars(env, name, NULL); 391 const char *hint = SDL_GetHint(utfname); 392 393 jstring result = (*env)->NewStringUTF(env, hint); 394 (*env)->ReleaseStringUTFChars(env, name, utfname); 395 396 return result; 397} 398 399/******************************************************************************* 400 Functions called by SDL into Java 401*******************************************************************************/ 402 403static int s_active = 0; 404struct LocalReferenceHolder 405{ 406 JNIEnv *m_env; 407 const char *m_func; 408}; 409 410static struct LocalReferenceHolder LocalReferenceHolder_Setup(const char *func) 411{ 412 struct LocalReferenceHolder refholder; 413 refholder.m_env = NULL; 414 refholder.m_func = func; 415#ifdef DEBUG_JNI 416 SDL_Log("Entering function %s", func); 417#endif 418 return refholder; 419} 420 421static SDL_bool LocalReferenceHolder_Init(struct LocalReferenceHolder *refholder, JNIEnv *env) 422{ 423 const int capacity = 16; 424 if ((*env)->PushLocalFrame(env, capacity) < 0) { 425 SDL_SetError("Failed to allocate enough JVM local references"); 426 return SDL_FALSE; 427 } 428 ++s_active; 429 refholder->m_env = env; 430 return SDL_TRUE; 431} 432 433static void LocalReferenceHolder_Cleanup(struct LocalReferenceHolder *refholder) 434{ 435#ifdef DEBUG_JNI 436 SDL_Log("Leaving function %s", refholder->m_func); 437#endif 438 if (refholder->m_env) { 439 JNIEnv* env = refholder->m_env; 440 (*env)->PopLocalFrame(env, NULL); 441 --s_active; 442 } 443} 444 445static SDL_bool LocalReferenceHolder_IsActive() 446{ 447 return s_active > 0; 448} 449 450ANativeWindow* Android_JNI_GetNativeWindow(void) 451{ 452 ANativeWindow* anw; 453 jobject s; 454 JNIEnv *env = Android_JNI_GetEnv(); 455 456 s = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetNativeSurface); 457 anw = ANativeWindow_fromSurface(env, s); 458 (*env)->DeleteLocalRef(env, s); 459 460 return anw; 461} 462 463void Android_JNI_SwapWindow() 464{ 465 JNIEnv *mEnv = Android_JNI_GetEnv(); 466 (*mEnv)->CallStaticVoidMethod(mEnv, mActivityClass, midFlipBuffers); 467} 468 469void Android_JNI_SetActivityTitle(const char *title) 470{ 471 jmethodID mid; 472 JNIEnv *mEnv = Android_JNI_GetEnv(); 473 mid = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,"setActivityTitle","(Ljava/lang/String;)Z"); 474 if (mid) { 475 jstring jtitle = (jstring)((*mEnv)->NewStringUTF(mEnv, title)); 476 (*mEnv)->CallStaticBooleanMethod(mEnv, mActivityClass, mid, jtitle); 477 (*mEnv)->DeleteLocalRef(mEnv, jtitle); 478 } 479} 480 481SDL_bool Android_JNI_GetAccelerometerValues(float values[3]) 482{ 483 int i; 484 SDL_bool retval = SDL_FALSE; 485 486 if (bHasNewData) { 487 for (i = 0; i < 3; ++i) { 488 values[i] = fLastAccelerometer[i]; 489 } 490 bHasNewData = false; 491 retval = SDL_TRUE; 492 } 493 494 return retval; 495} 496 497static void Android_JNI_ThreadDestroyed(void* value) 498{ 499 /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */ 500 JNIEnv *env = (JNIEnv*) value; 501 if (env != NULL) { 502 (*mJavaVM)->DetachCurrentThread(mJavaVM); 503 pthread_setspecific(mThreadKey, NULL); 504 } 505} 506 507JNIEnv* Android_JNI_GetEnv(void) 508{ 509 /* From http://developer.android.com/guide/practices/jni.html 510 * All threads are Linux threads, scheduled by the kernel. 511 * They're usually started from managed code (using Thread.start), but they can also be created elsewhere and then 512 * attached to the JavaVM. For example, a thread started with pthread_create can be attached with the 513 * JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a thread is attached, it has no JNIEnv, 514 * and cannot make JNI calls. 515 * Attaching a natively-created thread causes a java.lang.Thread object to be constructed and added to the "main" 516 * ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread on an already-attached thread 517 * is a no-op. 518 * Note: You can call this function any number of times for the same thread, there's no harm in it 519 */ 520 521 JNIEnv *env; 522 int status = (*mJavaVM)->AttachCurrentThread(mJavaVM, &env, NULL); 523 if(status < 0) { 524 LOGE("failed to attach current thread"); 525 return 0; 526 } 527 528 /* From http://developer.android.com/guide/practices/jni.html 529 * Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward, 530 * in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be 531 * called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific 532 * to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.) 533 * Note: The destructor is not called unless the stored value is != NULL 534 * Note: You can call this function any number of times for the same thread, there's no harm in it 535 * (except for some lost CPU cycles) 536 */ 537 pthread_setspecific(mThreadKey, (void*) env); 538 539 return env; 540} 541 542int Android_JNI_SetupThread(void) 543{ 544 Android_JNI_GetEnv(); 545 return 1; 546} 547 548/* 549 * Audio support 550 */ 551static jboolean audioBuffer16Bit = JNI_FALSE; 552static jboolean audioBufferStereo = JNI_FALSE; 553static jobject audioBuffer = NULL; 554static void* audioBufferPinned = NULL; 555 556int Android_JNI_OpenAudioDevice(int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames) 557{ 558 int audioBufferFrames; 559 560 JNIEnv *env = Android_JNI_GetEnv(); 561 562 if (!env) { 563 LOGE("callback_handler: failed to attach current thread"); 564 } 565 Android_JNI_SetupThread(); 566 567 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device"); 568 audioBuffer16Bit = is16Bit; 569 audioBufferStereo = channelCount > 1; 570 571 if ((*env)->CallStaticIntMethod(env, mActivityClass, midAudioInit, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames) != 0) { 572 /* Error during audio initialization */ 573 __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: error on AudioTrack initialization!"); 574 return 0; 575 } 576 577 /* Allocating the audio buffer from the Java side and passing it as the return value for audioInit no longer works on 578 * Android >= 4.2 due to a "stale global reference" error. So now we allocate this buffer directly from this side. */ 579 580 if (is16Bit) { 581 jshortArray audioBufferLocal = (*env)->NewShortArray(env, desiredBufferFrames * (audioBufferStereo ? 2 : 1)); 582 if (audioBufferLocal) { 583 audioBuffer = (*env)->NewGlobalRef(env, audioBufferLocal); 584 (*env)->DeleteLocalRef(env, audioBufferLocal); 585 } 586 } 587 else { 588 jbyteArray audioBufferLocal = (*env)->NewByteArray(env, desiredBufferFrames * (audioBufferStereo ? 2 : 1)); 589 if (audioBufferLocal) { 590 audioBuffer = (*env)->NewGlobalRef(env, audioBufferLocal); 591 (*env)->DeleteLocalRef(env, audioBufferLocal); 592 } 593 } 594 595 if (audioBuffer == NULL) { 596 __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: could not allocate an audio buffer!"); 597 return 0; 598 } 599 600 jboolean isCopy = JNI_FALSE; 601 if (audioBuffer16Bit) { 602 audioBufferPinned = (*env)->GetShortArrayElements(env, (jshortArray)audioBuffer, &isCopy); 603 audioBufferFrames = (*env)->GetArrayLength(env, (jshortArray)audioBuffer); 604 } else { 605 audioBufferPinned = (*env)->GetByteArrayElements(env, (jbyteArray)audioBuffer, &isCopy); 606 audioBufferFrames = (*env)->GetArrayLength(env, (jbyteArray)audioBuffer); 607 } 608 if (audioBufferStereo) { 609 audioBufferFrames /= 2; 610 } 611 612 return audioBufferFrames; 613} 614 615void * Android_JNI_GetAudioBuffer() 616{ 617 return audioBufferPinned; 618} 619 620void Android_JNI_WriteAudioBuffer() 621{ 622 JNIEnv *mAudioEnv = Android_JNI_GetEnv(); 623 624 if (audioBuffer16Bit) { 625 (*mAudioEnv)->ReleaseShortArrayElements(mAudioEnv, (jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT); 626 (*mAudioEnv)->CallStaticVoidMethod(mAudioEnv, mActivityClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer); 627 } else { 628 (*mAudioEnv)->ReleaseByteArrayElements(mAudioEnv, (jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT); 629 (*mAudioEnv)->CallStaticVoidMethod(mAudioEnv, mActivityClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer); 630 } 631 632 /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */ 633} 634 635void Android_JNI_CloseAudioDevice() 636{ 637 JNIEnv *env = Android_JNI_GetEnv(); 638 639 (*env)->CallStaticVoidMethod(env, mActivityClass, midAudioQuit); 640 641 if (audioBuffer) { 642 (*env)->DeleteGlobalRef(env, audioBuffer); 643 audioBuffer = NULL; 644 audioBufferPinned = NULL; 645 } 646} 647 648/* Test for an exception and call SDL_SetError with its detail if one occurs */ 649/* If the parameter silent is truthy then SDL_SetError() will not be called. */ 650static bool Android_JNI_ExceptionOccurred(bool silent) 651{ 652 SDL_assert(LocalReferenceHolder_IsActive()); 653 JNIEnv *mEnv = Android_JNI_GetEnv(); 654 655 jthrowable exception = (*mEnv)->ExceptionOccurred(mEnv); 656 if (exception != NULL) { 657 jmethodID mid; 658 659 /* Until this happens most JNI operations have undefined behaviour */ 660 (*mEnv)->ExceptionClear(mEnv); 661 662 if (!silent) { 663 jclass exceptionClass = (*mEnv)->GetObjectClass(mEnv, exception); 664 jclass classClass = (*mEnv)->FindClass(mEnv, "java/lang/Class"); 665 666 mid = (*mEnv)->GetMethodID(mEnv, classClass, "getName", "()Ljava/lang/String;"); 667 jstring exceptionName = (jstring)(*mEnv)->CallObjectMethod(mEnv, exceptionClass, mid); 668 const char* exceptionNameUTF8 = (*mEnv)->GetStringUTFChars(mEnv, exceptionName, 0); 669 670 mid = (*mEnv)->GetMethodID(mEnv, exceptionClass, "getMessage", "()Ljava/lang/String;"); 671 jstring exceptionMessage = (jstring)(*mEnv)->CallObjectMethod(mEnv, exception, mid); 672 673 if (exceptionMessage != NULL) { 674 const char* exceptionMessageUTF8 = (*mEnv)->GetStringUTFChars(mEnv, exceptionMessage, 0); 675 SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8); 676 (*mEnv)->ReleaseStringUTFChars(mEnv, exceptionMessage, exceptionMessageUTF8); 677 } else { 678 SDL_SetError("%s", exceptionNameUTF8); 679 } 680 681 (*mEnv)->ReleaseStringUTFChars(mEnv, exceptionName, exceptionNameUTF8); 682 } 683 684 return true; 685 } 686 687 return false; 688} 689 690static int Internal_Android_JNI_FileOpen(SDL_RWops* ctx) 691{ 692 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); 693 694 int result = 0; 695 696 jmethodID mid; 697 jobject context; 698 jobject assetManager; 699 jobject inputStream; 700 jclass channels; 701 jobject readableByteChannel; 702 jstring fileNameJString; 703 jobject fd; 704 jclass fdCls; 705 jfieldID descriptor; 706 707 JNIEnv *mEnv = Android_JNI_GetEnv(); 708 if (!LocalReferenceHolder_Init(&refs, mEnv)) { 709 goto failure; 710 } 711 712 fileNameJString = (jstring)ctx->hidden.androidio.fileNameRef; 713 ctx->hidden.androidio.position = 0; 714 715 /* context = SDLActivity.getContext(); */ 716 mid = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, 717 "getContext","()Landroid/content/Context;"); 718 context = (*mEnv)->CallStaticObjectMethod(mEnv, mActivityClass, mid); 719 720 721 /* assetManager = context.getAssets(); */ 722 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, context), 723 "getAssets", "()Landroid/content/res/AssetManager;"); 724 assetManager = (*mEnv)->CallObjectMethod(mEnv, context, mid); 725 726 /* First let's try opening the file to obtain an AssetFileDescriptor. 727 * This method reads the files directly from the APKs using standard *nix calls 728 */ 729 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, assetManager), "openFd", "(Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;"); 730 inputStream = (*mEnv)->CallObjectMethod(mEnv, assetManager, mid, fileNameJString); 731 if (Android_JNI_ExceptionOccurred(true)) { 732 goto fallback; 733 } 734 735 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getStartOffset", "()J"); 736 ctx->hidden.androidio.offset = (*mEnv)->CallLongMethod(mEnv, inputStream, mid); 737 if (Android_JNI_ExceptionOccurred(true)) { 738 goto fallback; 739 } 740 741 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getDeclaredLength", "()J"); 742 ctx->hidden.androidio.size = (*mEnv)->CallLongMethod(mEnv, inputStream, mid); 743 if (Android_JNI_ExceptionOccurred(true)) { 744 goto fallback; 745 } 746 747 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getFileDescriptor", "()Ljava/io/FileDescriptor;"); 748 fd = (*mEnv)->CallObjectMethod(mEnv, inputStream, mid); 749 fdCls = (*mEnv)->GetObjectClass(mEnv, fd); 750 descriptor = (*mEnv)->GetFieldID(mEnv, fdCls, "descriptor", "I"); 751 ctx->hidden.androidio.fd = (*mEnv)->GetIntField(mEnv, fd, descriptor); 752 ctx->hidden.androidio.assetFileDescriptorRef = (*mEnv)->NewGlobalRef(mEnv, inputStream); 753 754 /* Seek to the correct offset in the file. */ 755 lseek(ctx->hidden.androidio.fd, (off_t)ctx->hidden.androidio.offset, SEEK_SET); 756 757 if (false) { 758fallback: 759 /* Disabled log message because of spam on the Nexus 7 */ 760 /* __android_log_print(ANDROID_LOG_DEBUG, "SDL", "Falling back to legacy InputStream method for opening file"); */ 761 762 /* Try the old method using InputStream */ 763 ctx->hidden.androidio.assetFileDescriptorRef = NULL; 764 765 /* inputStream = assetManager.open(<filename>); */ 766 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, assetManager), 767 "open", "(Ljava/lang/String;I)Ljava/io/InputStream;"); 768 inputStream = (*mEnv)->CallObjectMethod(mEnv, assetManager, mid, fileNameJString, 1 /* ACCESS_RANDOM */); 769 if (Android_JNI_ExceptionOccurred(false)) { 770 // Try fallback to APK Extension files 771 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, context), 772 "openAPKExtensionInputStream", "(Ljava/lang/String;)Ljava/io/InputStream;"); 773 inputStream = (*mEnv)->CallObjectMethod(mEnv, context, mid, fileNameJString); 774 775 if (Android_JNI_ExceptionOccurred(false)) { 776 goto failure; 777 } 778 } 779 780 ctx->hidden.androidio.inputStreamRef = (*mEnv)->NewGlobalRef(mEnv, inputStream); 781 782 /* Despite all the visible documentation on [Asset]InputStream claiming 783 * that the .available() method is not guaranteed to return the entire file 784 * size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ... 785 * android/apis/content/ReadAsset.java imply that Android's 786 * AssetInputStream.available() /will/ always return the total file size 787 */ 788 789 /* size = inputStream.available(); */ 790 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), 791 "available", "()I"); 792 ctx->hidden.androidio.size = (long)(*mEnv)->CallIntMethod(mEnv, inputStream, mid); 793 if (Android_JNI_ExceptionOccurred(false)) { 794 goto failure; 795 } 796 797 /* readableByteChannel = Channels.newChannel(inputStream); */ 798 channels = (*mEnv)->FindClass(mEnv, "java/nio/channels/Channels"); 799 mid = (*mEnv)->GetStaticMethodID(mEnv, channels, 800 "newChannel", 801 "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;"); 802 readableByteChannel = (*mEnv)->CallStaticObjectMethod( 803 mEnv, channels, mid, inputStream); 804 if (Android_JNI_ExceptionOccurred(false)) { 805 goto failure; 806 } 807 808 ctx->hidden.androidio.readableByteChannelRef = 809 (*mEnv)->NewGlobalRef(mEnv, readableByteChannel); 810 811 /* Store .read id for reading purposes */ 812 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, readableByteChannel), 813 "read", "(Ljava/nio/ByteBuffer;)I"); 814 ctx->hidden.androidio.readMethod = mid; 815 } 816 817 if (false) { 818failure: 819 result = -1; 820 821 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.fileNameRef); 822 823 if(ctx->hidden.androidio.inputStreamRef != NULL) { 824 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.inputStreamRef); 825 } 826 827 if(ctx->hidden.androidio.readableByteChannelRef != NULL) { 828 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.readableByteChannelRef); 829 } 830 831 if(ctx->hidden.androidio.assetFileDescriptorRef != NULL) { 832 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.assetFileDescriptorRef); 833 } 834 835 } 836 837 LocalReferenceHolder_Cleanup(&refs); 838 return result; 839} 840 841int Android_JNI_FileOpen(SDL_RWops* ctx, 842 const char* fileName, const char* mode) 843{ 844 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); 845 JNIEnv *mEnv = Android_JNI_GetEnv(); 846 int retval; 847 848 if (!LocalReferenceHolder_Init(&refs, mEnv)) { 849 LocalReferenceHolder_Cleanup(&refs); 850 return -1; 851 } 852 853 if (!ctx) { 854 LocalReferenceHolder_Cleanup(&refs); 855 return -1; 856 } 857 858 jstring fileNameJString = (*mEnv)->NewStringUTF(mEnv, fileName); 859 ctx->hidden.androidio.fileNameRef = (*mEnv)->NewGlobalRef(mEnv, fileNameJString); 860 ctx->hidden.androidio.inputStreamRef = NULL; 861 ctx->hidden.androidio.readableByteChannelRef = NULL; 862 ctx->hidden.androidio.readMethod = NULL; 863 ctx->hidden.androidio.assetFileDescriptorRef = NULL; 864 865 retval = Internal_Android_JNI_FileOpen(ctx); 866 LocalReferenceHolder_Cleanup(&refs); 867 return retval; 868} 869 870size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer, 871 size_t size, size_t maxnum) 872{ 873 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); 874 875 if (ctx->hidden.androidio.assetFileDescriptorRef) { 876 size_t bytesMax = size * maxnum; 877 if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && ctx->hidden.androidio.position + bytesMax > ctx->hidden.androidio.size) { 878 bytesMax = ctx->hidden.androidio.size - ctx->hidden.androidio.position; 879 } 880 size_t result = read(ctx->hidden.androidio.fd, buffer, bytesMax ); 881 if (result > 0) { 882 ctx->hidden.androidio.position += result; 883 LocalReferenceHolder_Cleanup(&refs); 884 return result / size; 885 } 886 LocalReferenceHolder_Cleanup(&refs); 887 return 0; 888 } else { 889 jlong bytesRemaining = (jlong) (size * maxnum); 890 jlong bytesMax = (jlong) (ctx->hidden.androidio.size - ctx->hidden.androidio.position); 891 int bytesRead = 0; 892 893 /* Don't read more bytes than those that remain in the file, otherwise we get an exception */ 894 if (bytesRemaining > bytesMax) bytesRemaining = bytesMax; 895 896 JNIEnv *mEnv = Android_JNI_GetEnv(); 897 if (!LocalReferenceHolder_Init(&refs, mEnv)) { 898 LocalReferenceHolder_Cleanup(&refs); 899 return 0; 900 } 901 902 jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannelRef; 903 jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod; 904 jobject byteBuffer = (*mEnv)->NewDirectByteBuffer(mEnv, buffer, bytesRemaining); 905 906 while (bytesRemaining > 0) { 907 /* result = readableByteChannel.read(...); */ 908 int result = (*mEnv)->CallIntMethod(mEnv, readableByteChannel, readMethod, byteBuffer); 909 910 if (Android_JNI_ExceptionOccurred(false)) { 911 LocalReferenceHolder_Cleanup(&refs); 912 return 0; 913 } 914 915 if (result < 0) { 916 break; 917 } 918 919 bytesRemaining -= result; 920 bytesRead += result; 921 ctx->hidden.androidio.position += result; 922 } 923 LocalReferenceHolder_Cleanup(&refs); 924 return bytesRead / size; 925 } 926} 927 928size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer, 929 size_t size, size_t num) 930{ 931 SDL_SetError("Cannot write to Android package filesystem"); 932 return 0; 933} 934 935static int Internal_Android_JNI_FileClose(SDL_RWops* ctx, bool release) 936{ 937 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); 938 939 int result = 0; 940 JNIEnv *mEnv = Android_JNI_GetEnv(); 941 942 if (!LocalReferenceHolder_Init(&refs, mEnv)) { 943 LocalReferenceHolder_Cleanup(&refs); 944 return SDL_SetError("Failed to allocate enough JVM local references"); 945 } 946 947 if (ctx) { 948 if (release) { 949 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.fileNameRef); 950 } 951 952 if (ctx->hidden.androidio.assetFileDescriptorRef) { 953 jobject inputStream = (jobject)ctx->hidden.androidio.assetFileDescriptorRef; 954 jmethodID mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), 955 "close", "()V"); 956 (*mEnv)->CallVoidMethod(mEnv, inputStream, mid); 957 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.assetFileDescriptorRef); 958 if (Android_JNI_ExceptionOccurred(false)) { 959 result = -1; 960 } 961 } 962 else { 963 jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef; 964 965 /* inputStream.close(); */ 966 jmethodID mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), 967 "close", "()V"); 968 (*mEnv)->CallVoidMethod(mEnv, inputStream, mid); 969 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.inputStreamRef); 970 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.readableByteChannelRef); 971 if (Android_JNI_ExceptionOccurred(false)) { 972 result = -1; 973 } 974 } 975 976 if (release) { 977 SDL_FreeRW(ctx); 978 } 979 } 980 981 LocalReferenceHolder_Cleanup(&refs); 982 return result; 983} 984 985 986Sint64 Android_JNI_FileSize(SDL_RWops* ctx) 987{ 988 return ctx->hidden.androidio.size; 989} 990 991Sint64 Android_JNI_FileSeek(SDL_RWops* ctx, Sint64 offset, int whence) 992{ 993 if (ctx->hidden.androidio.assetFileDescriptorRef) { 994 switch (whence) { 995 case RW_SEEK_SET: 996 if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size; 997 offset += ctx->hidden.androidio.offset; 998 break; 999 case RW_SEEK_CUR: 1000 offset += ctx->hidden.androidio.position; 1001 if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size; 1002 offset += ctx->hidden.androidio.offset; 1003 break; 1004 case RW_SEEK_END: 1005 offset = ctx->hidden.androidio.offset + ctx->hidden.androidio.size + offset; 1006 break; 1007 default: 1008 return SDL_SetError("Unknown value for 'whence'"); 1009 } 1010 whence = SEEK_SET; 1011 1012 off_t ret = lseek(ctx->hidden.androidio.fd, (off_t)offset, SEEK_SET); 1013 if (ret == -1) return -1; 1014 ctx->hidden.androidio.position = ret - ctx->hidden.androidio.offset; 1015 } else { 1016 Sint64 newPosition; 1017 1018 switch (whence) { 1019 case RW_SEEK_SET: 1020 newPosition = offset; 1021 break; 1022 case RW_SEEK_CUR: 1023 newPosition = ctx->hidden.androidio.position + offset; 1024 break; 1025 case RW_SEEK_END: 1026 newPosition = ctx->hidden.androidio.size + offset; 1027 break; 1028 default: 1029 return SDL_SetError("Unknown value for 'whence'"); 1030 } 1031 1032 /* Validate the new position */ 1033 if (newPosition < 0) { 1034 return SDL_Error(SDL_EFSEEK); 1035 } 1036 if (newPosition > ctx->hidden.androidio.size) { 1037 newPosition = ctx->hidden.androidio.size; 1038 } 1039 1040 Sint64 movement = newPosition - ctx->hidden.androidio.position; 1041 if (movement > 0) { 1042 unsigned char buffer[4096]; 1043 1044 /* The easy case where we're seeking forwards */ 1045 while (movement > 0) { 1046 Sint64 amount = sizeof (buffer); 1047 if (amount > movement) { 1048 amount = movement; 1049 } 1050 size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount); 1051 if (result <= 0) { 1052 /* Failed to read/skip the required amount, so fail */ 1053 return -1; 1054 } 1055 1056 movement -= result; 1057 } 1058 1059 } else if (movement < 0) { 1060 /* We can't seek backwards so we have to reopen the file and seek */ 1061 /* forwards which obviously isn't very efficient */ 1062 Internal_Android_JNI_FileClose(ctx, false); 1063 Internal_Android_JNI_FileOpen(ctx); 1064 Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET); 1065 } 1066 } 1067 1068 return ctx->hidden.androidio.position; 1069 1070} 1071 1072int Android_JNI_FileClose(SDL_RWops* ctx) 1073{ 1074 return Internal_Android_JNI_FileClose(ctx, true); 1075} 1076 1077/* returns a new global reference which needs to be released later */ 1078static jobject Android_JNI_GetSystemServiceObject(const char* name) 1079{ 1080 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); 1081 JNIEnv* env = Android_JNI_GetEnv(); 1082 jobject retval = NULL; 1083 1084 if (!LocalReferenceHolder_Init(&refs, env)) { 1085 LocalReferenceHolder_Cleanup(&refs); 1086 return NULL; 1087 } 1088 1089 jstring service = (*env)->NewStringUTF(env, name); 1090 1091 jmethodID mid; 1092 1093 mid = (*env)->GetStaticMethodID(env, mActivityClass, "getContext", "()Landroid/content/Context;"); 1094 jobject context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid); 1095 1096 mid = (*env)->GetMethodID(env, mActivityClass, "getSystemServiceFromUiThread", "(Ljava/lang/String;)Ljava/lang/Object;"); 1097 jobject manager = (*env)->CallObjectMethod(env, context, mid, service); 1098 1099 (*env)->DeleteLocalRef(env, service); 1100 1101 retval = manager ? (*env)->NewGlobalRef(env, manager) : NULL; 1102 LocalReferenceHolder_Cleanup(&refs); 1103 return retval; 1104} 1105 1106#define SETUP_CLIPBOARD(error) \ 1107 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); \ 1108 JNIEnv* env = Android_JNI_GetEnv(); \ 1109 if (!LocalReferenceHolder_Init(&refs, env)) { \ 1110 LocalReferenceHolder_Cleanup(&refs); \ 1111 return error; \ 1112 } \ 1113 jobject clipboard = Android_JNI_GetSystemServiceObject("clipboard"); \ 1114 if (!clipboard) { \ 1115 LocalReferenceHolder_Cleanup(&refs); \ 1116 return error; \ 1117 } 1118 1119#define CLEANUP_CLIPBOARD() \ 1120 LocalReferenceHolder_Cleanup(&refs); 1121 1122int Android_JNI_SetClipboardText(const char* text) 1123{ 1124 SETUP_CLIPBOARD(-1) 1125 1126 jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, clipboard), "setText", "(Ljava/lang/CharSequence;)V"); 1127 jstring string = (*env)->NewStringUTF(env, text); 1128 (*env)->CallVoidMethod(env, clipboard, mid, string); 1129 (*env)->DeleteGlobalRef(env, clipboard); 1130 (*env)->DeleteLocalRef(env, string); 1131 1132 CLEANUP_CLIPBOARD(); 1133 1134 return 0; 1135} 1136 1137char* Android_JNI_GetClipboardText() 1138{ 1139 SETUP_CLIPBOARD(SDL_strdup("")) 1140 1141 jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, clipboard), "getText", "()Ljava/lang/CharSequence;"); 1142 jobject sequence = (*env)->CallObjectMethod(env, clipboard, mid); 1143 (*env)->DeleteGlobalRef(env, clipboard); 1144 if (sequence) { 1145 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, sequence), "toString", "()Ljava/lang/String;"); 1146 jstring string = (jstring)((*env)->CallObjectMethod(env, sequence, mid)); 1147 const char* utf = (*env)->GetStringUTFChars(env, string, 0); 1148 if (utf) { 1149 char* text = SDL_strdup(utf); 1150 (*env)->ReleaseStringUTFChars(env, string, utf); 1151 1152 CLEANUP_CLIPBOARD(); 1153 1154 return text; 1155 } 1156 } 1157 1158 CLEANUP_CLIPBOARD(); 1159 1160 return SDL_strdup(""); 1161} 1162 1163SDL_bool Android_JNI_HasClipboardText() 1164{ 1165 SETUP_CLIPBOARD(SDL_FALSE) 1166 1167 jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, clipboard), "hasText", "()Z"); 1168 jboolean has = (*env)->CallBooleanMethod(env, clipboard, mid); 1169 (*env)->DeleteGlobalRef(env, clipboard); 1170 1171 CLEANUP_CLIPBOARD(); 1172 1173 return has ? SDL_TRUE : SDL_FALSE; 1174} 1175 1176 1177/* returns 0 on success or -1 on error (others undefined then) 1178 * returns truthy or falsy value in plugged, charged and battery 1179 * returns the value in seconds and percent or -1 if not available 1180 */ 1181int Android_JNI_GetPowerInfo(int* plugged, int* charged, int* battery, int* seconds, int* percent) 1182{ 1183 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); 1184 JNIEnv* env = Android_JNI_GetEnv(); 1185 if (!LocalReferenceHolder_Init(&refs, env)) { 1186 LocalReferenceHolder_Cleanup(&refs); 1187 return -1; 1188 } 1189 1190 jmethodID mid; 1191 1192 mid = (*env)->GetStaticMethodID(env, mActivityClass, "getContext", "()Landroid/content/Context;"); 1193 jobject context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid); 1194 1195 jstring action = (*env)->NewStringUTF(env, "android.intent.action.BATTERY_CHANGED"); 1196 1197 jclass cls = (*env)->FindClass(env, "android/content/IntentFilter"); 1198 1199 mid = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/lang/String;)V"); 1200 jobject filter = (*env)->NewObject(env, cls, mid, action); 1201 1202 (*env)->DeleteLocalRef(env, action); 1203 1204 mid = (*env)->GetMethodID(env, mActivityClass, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;"); 1205 jobject intent = (*env)->CallObjectMethod(env, context, mid, NULL, filter); 1206 1207 (*env)->DeleteLocalRef(env, filter); 1208 1209 cls = (*env)->GetObjectClass(env, intent); 1210 1211 jstring iname; 1212 jmethodID imid = (*env)->GetMethodID(env, cls, "getIntExtra", "(Ljava/lang/String;I)I"); 1213 1214#define GET_INT_EXTRA(var, key) \ 1215 iname = (*env)->NewStringUTF(env, key); \ 1216 int var = (*env)->CallIntMethod(env, intent, imid, iname, -1); \ 1217 (*env)->DeleteLocalRef(env, iname); 1218 1219 jstring bname; 1220 jmethodID bmid = (*env)->GetMethodID(env, cls, "getBooleanExtra", "(Ljava/lang/String;Z)Z"); 1221 1222#define GET_BOOL_EXTRA(var, key) \ 1223 bname = (*env)->NewStringUTF(env, key); \ 1224 int var = (*env)->CallBooleanMethod(env, intent, bmid, bname, JNI_FALSE); \ 1225 (*env)->DeleteLocalRef(env, bname); 1226 1227 if (plugged) { 1228 GET_INT_EXTRA(plug, "plugged") /* == BatteryManager.EXTRA_PLUGGED (API 5) */ 1229 if (plug == -1) { 1230 LocalReferenceHolder_Cleanup(&refs); 1231 return -1; 1232 } 1233 /* 1 == BatteryManager.BATTERY_PLUGGED_AC */ 1234 /* 2 == BatteryManager.BATTERY_PLUGGED_USB */ 1235 *plugged = (0 < plug) ? 1 : 0; 1236 } 1237 1238 if (charged) { 1239 GET_INT_EXTRA(status, "status") /* == BatteryManager.EXTRA_STATUS (API 5) */ 1240 if (status == -1) { 1241 LocalReferenceHolder_Cleanup(&refs); 1242 return -1; 1243 } 1244 /* 5 == BatteryManager.BATTERY_STATUS_FULL */ 1245 *charged = (status == 5) ? 1 : 0; 1246 } 1247 1248 if (battery) { 1249 GET_BOOL_EXTRA(present, "present") /* == BatteryManager.EXTRA_PRESENT (API 5) */ 1250 *battery = present ? 1 : 0; 1251 } 1252 1253 if (seconds) { 1254 *seconds = -1; /* not possible */ 1255 } 1256 1257 if (percent) { 1258 GET_INT_EXTRA(level, "level") /* == BatteryManager.EXTRA_LEVEL (API 5) */ 1259 GET_INT_EXTRA(scale, "scale") /* == BatteryManager.EXTRA_SCALE (API 5) */ 1260 if ((level == -1) || (scale == -1)) { 1261 LocalReferenceHolder_Cleanup(&refs); 1262 return -1; 1263 } 1264 *percent = level * 100 / scale; 1265 } 1266 1267 (*env)->DeleteLocalRef(env, intent); 1268 1269 LocalReferenceHolder_Cleanup(&refs); 1270 return 0; 1271} 1272 1273/* returns number of found touch devices as return value and ids in parameter ids */ 1274int Android_JNI_GetTouchDeviceIds(int **ids) { 1275 JNIEnv *env = Android_JNI_GetEnv(); 1276 jint sources = 4098; /* == InputDevice.SOURCE_TOUCHSCREEN */ 1277 jmethodID mid = (*env)->GetStaticMethodID(env, mActivityClass, "inputGetInputDeviceIds", "(I)[I"); 1278 jintArray array = (jintArray) (*env)->CallStaticObjectMethod(env, mActivityClass, mid, sources); 1279 int number = 0; 1280 *ids = NULL; 1281 if (array) { 1282 number = (int) (*env)->GetArrayLength(env, array); 1283 if (0 < number) { 1284 jint* elements = (*env)->GetIntArrayElements(env, array, NULL); 1285 if (elements) { 1286 int i; 1287 *ids = SDL_malloc(number * sizeof (**ids)); 1288 for (i = 0; i < number; ++i) { /* not assuming sizeof (jint) == sizeof (int) */ 1289 (*ids)[i] = elements[i]; 1290 } 1291 (*env)->ReleaseIntArrayElements(env, array, elements, JNI_ABORT); 1292 } 1293 } 1294 (*env)->DeleteLocalRef(env, array); 1295 } 1296 return number; 1297} 1298 1299void Android_JNI_PollInputDevices() 1300{ 1301 JNIEnv *env = Android_JNI_GetEnv(); 1302 (*env)->CallStaticVoidMethod(env, mActivityClass, midPollInputDevices); 1303} 1304 1305/* See SDLActivity.java for constants. */ 1306#define COMMAND_SET_KEEP_SCREEN_ON 5 1307 1308/* sends message to be handled on the UI event dispatch thread */ 1309int Android_JNI_SendMessage(int command, int param) 1310{ 1311 JNIEnv *env = Android_JNI_GetEnv(); 1312 if (!env) { 1313 return -1; 1314 } 1315 jmethodID mid = (*env)->GetStaticMethodID(env, mActivityClass, "sendMessage", "(II)Z"); 1316 if (!mid) { 1317 return -1; 1318 } 1319 jboolean success = (*env)->CallStaticBooleanMethod(env, mActivityClass, mid, command, param); 1320 return success ? 0 : -1; 1321} 1322 1323void Android_JNI_SuspendScreenSaver(SDL_bool suspend) 1324{ 1325 Android_JNI_SendMessage(COMMAND_SET_KEEP_SCREEN_ON, (suspend == SDL_FALSE) ? 0 : 1); 1326} 1327 1328void Android_JNI_ShowTextInput(SDL_Rect *inputRect) 1329{ 1330 JNIEnv *env = Android_JNI_GetEnv(); 1331 if (!env) { 1332 return; 1333 } 1334 1335 jmethodID mid = (*env)->GetStaticMethodID(env, mActivityClass, "showTextInput", "(IIII)Z"); 1336 if (!mid) { 1337 return; 1338 } 1339 (*env)->CallStaticBooleanMethod(env, mActivityClass, mid, 1340 inputRect->x, 1341 inputRect->y, 1342 inputRect->w, 1343 inputRect->h ); 1344} 1345 1346void Android_JNI_HideTextInput() 1347{ 1348 /* has to match Activity constant */ 1349 const int COMMAND_TEXTEDIT_HIDE = 3; 1350 Android_JNI_SendMessage(COMMAND_TEXTEDIT_HIDE, 0); 1351} 1352 1353int Android_JNI_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid) 1354{ 1355 JNIEnv *env; 1356 jmethodID mid; 1357 jobject context; 1358 jstring title; 1359 jstring message; 1360 jintArray button_flags; 1361 jintArray button_ids; 1362 jobjectArray button_texts; 1363 jintArray colors; 1364 jint temp; 1365 int i; 1366 1367 env = Android_JNI_GetEnv(); 1368 1369 /* convert parameters */ 1370 1371 title = (*env)->NewStringUTF(env, messageboxdata->title); 1372 message = (*env)->NewStringUTF(env, messageboxdata->message); 1373 1374 button_flags = (*env)->NewIntArray(env, messageboxdata->numbuttons); 1375 button_ids = (*env)->NewIntArray(env, messageboxdata->numbuttons); 1376 button_texts = (*env)->NewObjectArray(env, messageboxdata->numbuttons, 1377 (*env)->FindClass(env, "java/lang/String"), NULL); 1378 for (i = 0; i < messageboxdata->numbuttons; ++i) { 1379 temp = messageboxdata->buttons[i].flags; 1380 (*env)->SetIntArrayRegion(env, button_flags, i, 1, &temp); 1381 temp = messageboxdata->buttons[i].buttonid; 1382 (*env)->SetIntArrayRegion(env, button_ids, i, 1, &temp); 1383 (*env)->SetObjectArrayElement(env, button_texts, i, (*env)->NewStringUTF(env, messageboxdata->buttons[i].text)); 1384 } 1385 1386 if (messageboxdata->colorScheme) { 1387 colors = (*env)->NewIntArray(env, SDL_MESSAGEBOX_COLOR_MAX); 1388 for (i = 0; i < SDL_MESSAGEBOX_COLOR_MAX; ++i) { 1389 temp = (0xFF << 24) | 1390 (messageboxdata->colorScheme->colors[i].r << 16) | 1391 (messageboxdata->colorScheme->colors[i].g << 8) | 1392 (messageboxdata->colorScheme->colors[i].b << 0); 1393 (*env)->SetIntArrayRegion(env, colors, i, 1, &temp); 1394 } 1395 } else { 1396 colors = NULL; 1397 } 1398 1399 /* call function */ 1400 1401 mid = (*env)->GetStaticMethodID(env, mActivityClass, "getContext","()Landroid/content/Context;"); 1402 1403 context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid); 1404 1405 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context), 1406 "messageboxShowMessageBox", "(ILjava/lang/String;Ljava/lang/String;[I[I[Ljava/lang/String;[I)I"); 1407 *buttonid = (*env)->CallIntMethod(env, context, mid, 1408 messageboxdata->flags, 1409 title, 1410 message, 1411 button_flags, 1412 button_ids, 1413 button_texts, 1414 colors); 1415 1416 /* delete parameters */ 1417 1418 (*env)->DeleteLocalRef(env, title); 1419 (*env)->DeleteLocalRef(env, message); 1420 (*env)->DeleteLocalRef(env, button_flags); 1421 (*env)->DeleteLocalRef(env, button_ids); 1422 for (i = 0; i < messageboxdata->numbuttons; ++i) { 1423 (*env)->DeleteLocalRef(env, (*env)->GetObjectArrayElement(env, button_texts, i)); 1424 (*env)->SetObjectArrayElement(env, button_texts, i, NULL); 1425 } 1426 (*env)->DeleteLocalRef(env, button_texts); 1427 (*env)->DeleteLocalRef(env, colors); 1428 1429 return 0; 1430} 1431 1432/* 1433////////////////////////////////////////////////////////////////////////////// 1434// 1435// Functions exposed to SDL applications in SDL_system.h 1436////////////////////////////////////////////////////////////////////////////// 1437*/ 1438 1439void *SDL_AndroidGetJNIEnv() 1440{ 1441 return Android_JNI_GetEnv(); 1442} 1443 1444 1445 1446void *SDL_AndroidGetActivity() 1447{ 1448 /* See SDL_system.h for caveats on using this function. */ 1449 1450 jmethodID mid; 1451 1452 JNIEnv *env = Android_JNI_GetEnv(); 1453 if (!env) { 1454 return NULL; 1455 } 1456 1457 /* return SDLActivity.getContext(); */ 1458 mid = (*env)->GetStaticMethodID(env, mActivityClass, 1459 "getContext","()Landroid/content/Context;"); 1460 return (*env)->CallStaticObjectMethod(env, mActivityClass, mid); 1461} 1462 1463const char * SDL_AndroidGetInternalStoragePath() 1464{ 1465 static char *s_AndroidInternalFilesPath = NULL; 1466 1467 if (!s_AndroidInternalFilesPath) { 1468 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); 1469 jmethodID mid; 1470 jobject context; 1471 jobject fileObject; 1472 jstring pathString; 1473 const char *path; 1474 1475 JNIEnv *env = Android_JNI_GetEnv(); 1476 if (!LocalReferenceHolder_Init(&refs, env)) { 1477 LocalReferenceHolder_Cleanup(&refs); 1478 return NULL; 1479 } 1480 1481 /* context = SDLActivity.getContext(); */ 1482 mid = (*env)->GetStaticMethodID(env, mActivityClass, 1483 "getContext","()Landroid/content/Context;"); 1484 context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid); 1485 1486 /* fileObj = context.getFilesDir(); */ 1487 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context), 1488 "getFilesDir", "()Ljava/io/File;"); 1489 fileObject = (*env)->CallObjectMethod(env, context, mid); 1490 if (!fileObject) { 1491 SDL_SetError("Couldn't get internal directory"); 1492 LocalReferenceHolder_Cleanup(&refs); 1493 return NULL; 1494 } 1495 1496 /* path = fileObject.getAbsolutePath(); */ 1497 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject), 1498 "getAbsolutePath", "()Ljava/lang/String;"); 1499 pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid); 1500 1501 path = (*env)->GetStringUTFChars(env, pathString, NULL); 1502 s_AndroidInternalFilesPath = SDL_strdup(path); 1503 (*env)->ReleaseStringUTFChars(env, pathString, path); 1504 1505 LocalReferenceHolder_Cleanup(&refs); 1506 } 1507 return s_AndroidInternalFilesPath; 1508} 1509 1510int SDL_AndroidGetExternalStorageState() 1511{ 1512 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); 1513 jmethodID mid; 1514 jclass cls; 1515 jstring stateString; 1516 const char *state; 1517 int stateFlags; 1518 1519 JNIEnv *env = Android_JNI_GetEnv(); 1520 if (!LocalReferenceHolder_Init(&refs, env)) { 1521 LocalReferenceHolder_Cleanup(&refs); 1522 return 0; 1523 } 1524 1525 cls = (*env)->FindClass(env, "android/os/Environment"); 1526 mid = (*env)->GetStaticMethodID(env, cls, 1527 "getExternalStorageState", "()Ljava/lang/String;"); 1528 stateString = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid); 1529 1530 state = (*env)->GetStringUTFChars(env, stateString, NULL); 1531 1532 /* Print an info message so people debugging know the storage state */ 1533 __android_log_print(ANDROID_LOG_INFO, "SDL", "external storage state: %s", state); 1534 1535 if (SDL_strcmp(state, "mounted") == 0) { 1536 stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ | 1537 SDL_ANDROID_EXTERNAL_STORAGE_WRITE; 1538 } else if (SDL_strcmp(state, "mounted_ro") == 0) { 1539 stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ; 1540 } else { 1541 stateFlags = 0; 1542 } 1543 (*env)->ReleaseStringUTFChars(env, stateString, state); 1544 1545 LocalReferenceHolder_Cleanup(&refs); 1546 return stateFlags; 1547} 1548 1549const char * SDL_AndroidGetExternalStoragePath() 1550{ 1551 static char *s_AndroidExternalFilesPath = NULL; 1552 1553 if (!s_AndroidExternalFilesPath) { 1554 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); 1555 jmethodID mid; 1556 jobject context; 1557 jobject fileObject; 1558 jstring pathString; 1559 const char *path; 1560 1561 JNIEnv *env = Android_JNI_GetEnv(); 1562 if (!LocalReferenceHolder_Init(&refs, env)) { 1563 LocalReferenceHolder_Cleanup(&refs); 1564 return NULL; 1565 } 1566 1567 /* context = SDLActivity.getContext(); */ 1568 mid = (*env)->GetStaticMethodID(env, mActivityClass, 1569 "getContext","()Landroid/content/Context;"); 1570 context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid); 1571 1572 /* fileObj = context.getExternalFilesDir(); */ 1573 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context), 1574 "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;"); 1575 fileObject = (*env)->CallObjectMethod(env, context, mid, NULL); 1576 if (!fileObject) { 1577 SDL_SetError("Couldn't get external directory"); 1578 LocalReferenceHolder_Cleanup(&refs); 1579 return NULL; 1580 } 1581 1582 /* path = fileObject.getAbsolutePath(); */ 1583 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject), 1584 "getAbsolutePath", "()Ljava/lang/String;"); 1585 pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid); 1586 1587 path = (*env)->GetStringUTFChars(env, pathString, NULL); 1588 s_AndroidExternalFilesPath = SDL_strdup(path); 1589 (*env)->ReleaseStringUTFChars(env, pathString, path); 1590 1591 LocalReferenceHolder_Cleanup(&refs); 1592 } 1593 return s_AndroidExternalFilesPath; 1594} 1595 1596#endif /* __ANDROID__ */ 1597 1598/* vi: set ts=4 sw=4 expandtab: */ 1599