src/core/android/SDL_android.c
author Philipp Wiesemann <philipp.wiesemann@arcor.de>
Thu, 17 Sep 2015 22:30:24 +0200
changeset 9870 6dd5ab47534b
parent 9869 4ba43d626c4a
child 9873 b0f121cfa074
permissions -rw-r--r--
Android: Fixed trying to read from APK expansion files without version hint set.

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