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