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