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