src/core/android/SDL_android.c
author Philipp Wiesemann <philipp.wiesemann@arcor.de>
Tue, 09 Dec 2014 22:49:16 +0100
changeset 9268 7f2833a2191b
parent 9231 ec22701132b5
child 9271 8f71cb5dc099
permissions -rw-r--r--
Fixed bug 2811 - [patch] Android core: Fix JNI 'nativeGetHint' symbol not being exported

Jonas Kulla

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