src/core/android/SDL_android.c
author Philipp Wiesemann <philipp.wiesemann@arcor.de>
Fri, 18 Sep 2015 21:26:46 +0200
changeset 9873 b0f121cfa074
parent 9870 6dd5ab47534b
child 9880 3bbeb623181c
permissions -rw-r--r--
Android: Added check if Java method for APK expansion file exists.
     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             if (!mid) {
   792                 SDL_SetError("No openAPKExpansionInputStream() in Java class");
   793                 goto failure; /* Java class is missing the required method */
   794             }
   795             inputStream = (*mEnv)->CallObjectMethod(mEnv, context, mid, fileNameJString);
   796 
   797             /* Exception is checked first because it always needs to be cleared.
   798              * If no exception occurred then the last SDL error message is kept.
   799              */
   800             if (Android_JNI_ExceptionOccurred(SDL_FALSE) || !inputStream) {
   801                 goto failure;
   802             }
   803         }
   804 
   805         ctx->hidden.androidio.inputStreamRef = (*mEnv)->NewGlobalRef(mEnv, inputStream);
   806 
   807         /* Despite all the visible documentation on [Asset]InputStream claiming
   808          * that the .available() method is not guaranteed to return the entire file
   809          * size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
   810          * android/apis/content/ReadAsset.java imply that Android's
   811          * AssetInputStream.available() /will/ always return the total file size
   812         */
   813         
   814         /* size = inputStream.available(); */
   815         mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream),
   816                 "available", "()I");
   817         ctx->hidden.androidio.size = (long)(*mEnv)->CallIntMethod(mEnv, inputStream, mid);
   818         if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
   819             goto failure;
   820         }
   821 
   822         /* readableByteChannel = Channels.newChannel(inputStream); */
   823         channels = (*mEnv)->FindClass(mEnv, "java/nio/channels/Channels");
   824         mid = (*mEnv)->GetStaticMethodID(mEnv, channels,
   825                 "newChannel",
   826                 "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
   827         readableByteChannel = (*mEnv)->CallStaticObjectMethod(
   828                 mEnv, channels, mid, inputStream);
   829         if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
   830             goto failure;
   831         }
   832 
   833         ctx->hidden.androidio.readableByteChannelRef =
   834             (*mEnv)->NewGlobalRef(mEnv, readableByteChannel);
   835 
   836         /* Store .read id for reading purposes */
   837         mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, readableByteChannel),
   838                 "read", "(Ljava/nio/ByteBuffer;)I");
   839         ctx->hidden.androidio.readMethod = mid;
   840     }
   841 
   842     if (0) {
   843 failure:
   844         result = -1;
   845 
   846         (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.fileNameRef);
   847 
   848         if(ctx->hidden.androidio.inputStreamRef != NULL) {
   849             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.inputStreamRef);
   850         }
   851 
   852         if(ctx->hidden.androidio.readableByteChannelRef != NULL) {
   853             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.readableByteChannelRef);
   854         }
   855 
   856         if(ctx->hidden.androidio.assetFileDescriptorRef != NULL) {
   857             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.assetFileDescriptorRef);
   858         }
   859 
   860     }
   861     
   862     LocalReferenceHolder_Cleanup(&refs);
   863     return result;
   864 }
   865 
   866 int Android_JNI_FileOpen(SDL_RWops* ctx,
   867         const char* fileName, const char* mode)
   868 {
   869     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
   870     JNIEnv *mEnv = Android_JNI_GetEnv();
   871     int retval;
   872 
   873     if (!LocalReferenceHolder_Init(&refs, mEnv)) {
   874         LocalReferenceHolder_Cleanup(&refs);        
   875         return -1;
   876     }
   877 
   878     if (!ctx) {
   879         LocalReferenceHolder_Cleanup(&refs);
   880         return -1;
   881     }
   882 
   883     jstring fileNameJString = (*mEnv)->NewStringUTF(mEnv, fileName);
   884     ctx->hidden.androidio.fileNameRef = (*mEnv)->NewGlobalRef(mEnv, fileNameJString);
   885     ctx->hidden.androidio.inputStreamRef = NULL;
   886     ctx->hidden.androidio.readableByteChannelRef = NULL;
   887     ctx->hidden.androidio.readMethod = NULL;
   888     ctx->hidden.androidio.assetFileDescriptorRef = NULL;
   889 
   890     retval = Internal_Android_JNI_FileOpen(ctx);
   891     LocalReferenceHolder_Cleanup(&refs);
   892     return retval;
   893 }
   894 
   895 size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
   896         size_t size, size_t maxnum)
   897 {
   898     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
   899 
   900     if (ctx->hidden.androidio.assetFileDescriptorRef) {
   901         size_t bytesMax = size * maxnum;
   902         if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && ctx->hidden.androidio.position + bytesMax > ctx->hidden.androidio.size) {
   903             bytesMax = ctx->hidden.androidio.size - ctx->hidden.androidio.position;
   904         }
   905         size_t result = read(ctx->hidden.androidio.fd, buffer, bytesMax );
   906         if (result > 0) {
   907             ctx->hidden.androidio.position += result;
   908             LocalReferenceHolder_Cleanup(&refs);
   909             return result / size;
   910         }
   911         LocalReferenceHolder_Cleanup(&refs);
   912         return 0;
   913     } else {
   914         jlong bytesRemaining = (jlong) (size * maxnum);
   915         jlong bytesMax = (jlong) (ctx->hidden.androidio.size -  ctx->hidden.androidio.position);
   916         int bytesRead = 0;
   917 
   918         /* Don't read more bytes than those that remain in the file, otherwise we get an exception */
   919         if (bytesRemaining >  bytesMax) bytesRemaining = bytesMax;
   920 
   921         JNIEnv *mEnv = Android_JNI_GetEnv();
   922         if (!LocalReferenceHolder_Init(&refs, mEnv)) {
   923             LocalReferenceHolder_Cleanup(&refs);            
   924             return 0;
   925         }
   926 
   927         jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannelRef;
   928         jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
   929         jobject byteBuffer = (*mEnv)->NewDirectByteBuffer(mEnv, buffer, bytesRemaining);
   930 
   931         while (bytesRemaining > 0) {
   932             /* result = readableByteChannel.read(...); */
   933             int result = (*mEnv)->CallIntMethod(mEnv, readableByteChannel, readMethod, byteBuffer);
   934 
   935             if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
   936                 LocalReferenceHolder_Cleanup(&refs);            
   937                 return 0;
   938             }
   939 
   940             if (result < 0) {
   941                 break;
   942             }
   943 
   944             bytesRemaining -= result;
   945             bytesRead += result;
   946             ctx->hidden.androidio.position += result;
   947         }
   948         LocalReferenceHolder_Cleanup(&refs);                    
   949         return bytesRead / size;
   950     }
   951 }
   952 
   953 size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
   954         size_t size, size_t num)
   955 {
   956     SDL_SetError("Cannot write to Android package filesystem");
   957     return 0;
   958 }
   959 
   960 static int Internal_Android_JNI_FileClose(SDL_RWops* ctx, SDL_bool release)
   961 {
   962     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
   963 
   964     int result = 0;
   965     JNIEnv *mEnv = Android_JNI_GetEnv();
   966 
   967     if (!LocalReferenceHolder_Init(&refs, mEnv)) {
   968         LocalReferenceHolder_Cleanup(&refs);
   969         return SDL_SetError("Failed to allocate enough JVM local references");
   970     }
   971 
   972     if (ctx) {
   973         if (release) {
   974             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.fileNameRef);
   975         }
   976 
   977         if (ctx->hidden.androidio.assetFileDescriptorRef) {
   978             jobject inputStream = (jobject)ctx->hidden.androidio.assetFileDescriptorRef;
   979             jmethodID mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream),
   980                     "close", "()V");
   981             (*mEnv)->CallVoidMethod(mEnv, inputStream, mid);
   982             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.assetFileDescriptorRef);
   983             if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
   984                 result = -1;
   985             }
   986         }
   987         else {
   988             jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
   989 
   990             /* inputStream.close(); */
   991             jmethodID mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream),
   992                     "close", "()V");
   993             (*mEnv)->CallVoidMethod(mEnv, inputStream, mid);
   994             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.inputStreamRef);
   995             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.readableByteChannelRef);
   996             if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
   997                 result = -1;
   998             }
   999         }
  1000 
  1001         if (release) {
  1002             SDL_FreeRW(ctx);
  1003         }
  1004     }
  1005 
  1006     LocalReferenceHolder_Cleanup(&refs);
  1007     return result;
  1008 }
  1009 
  1010 
  1011 Sint64 Android_JNI_FileSize(SDL_RWops* ctx)
  1012 {
  1013     return ctx->hidden.androidio.size;
  1014 }
  1015 
  1016 Sint64 Android_JNI_FileSeek(SDL_RWops* ctx, Sint64 offset, int whence)
  1017 {
  1018     if (ctx->hidden.androidio.assetFileDescriptorRef) {
  1019         switch (whence) {
  1020             case RW_SEEK_SET:
  1021                 if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
  1022                 offset += ctx->hidden.androidio.offset;
  1023                 break;
  1024             case RW_SEEK_CUR:
  1025                 offset += ctx->hidden.androidio.position;
  1026                 if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
  1027                 offset += ctx->hidden.androidio.offset;
  1028                 break;
  1029             case RW_SEEK_END:
  1030                 offset = ctx->hidden.androidio.offset + ctx->hidden.androidio.size + offset;
  1031                 break;
  1032             default:
  1033                 return SDL_SetError("Unknown value for 'whence'");
  1034         }
  1035         whence = SEEK_SET;
  1036 
  1037         off_t ret = lseek(ctx->hidden.androidio.fd, (off_t)offset, SEEK_SET);
  1038         if (ret == -1) return -1;
  1039         ctx->hidden.androidio.position = ret - ctx->hidden.androidio.offset;
  1040     } else {
  1041         Sint64 newPosition;
  1042 
  1043         switch (whence) {
  1044             case RW_SEEK_SET:
  1045                 newPosition = offset;
  1046                 break;
  1047             case RW_SEEK_CUR:
  1048                 newPosition = ctx->hidden.androidio.position + offset;
  1049                 break;
  1050             case RW_SEEK_END:
  1051                 newPosition = ctx->hidden.androidio.size + offset;
  1052                 break;
  1053             default:
  1054                 return SDL_SetError("Unknown value for 'whence'");
  1055         }
  1056 
  1057         /* Validate the new position */
  1058         if (newPosition < 0) {
  1059             return SDL_Error(SDL_EFSEEK);
  1060         }
  1061         if (newPosition > ctx->hidden.androidio.size) {
  1062             newPosition = ctx->hidden.androidio.size;
  1063         }
  1064 
  1065         Sint64 movement = newPosition - ctx->hidden.androidio.position;
  1066         if (movement > 0) {
  1067             unsigned char buffer[4096];
  1068 
  1069             /* The easy case where we're seeking forwards */
  1070             while (movement > 0) {
  1071                 Sint64 amount = sizeof (buffer);
  1072                 if (amount > movement) {
  1073                     amount = movement;
  1074                 }
  1075                 size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount);
  1076                 if (result <= 0) {
  1077                     /* Failed to read/skip the required amount, so fail */
  1078                     return -1;
  1079                 }
  1080 
  1081                 movement -= result;
  1082             }
  1083 
  1084         } else if (movement < 0) {
  1085             /* We can't seek backwards so we have to reopen the file and seek */
  1086             /* forwards which obviously isn't very efficient */
  1087             Internal_Android_JNI_FileClose(ctx, SDL_FALSE);
  1088             Internal_Android_JNI_FileOpen(ctx);
  1089             Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
  1090         }
  1091     }
  1092 
  1093     return ctx->hidden.androidio.position;
  1094 
  1095 }
  1096 
  1097 int Android_JNI_FileClose(SDL_RWops* ctx)
  1098 {
  1099     return Internal_Android_JNI_FileClose(ctx, SDL_TRUE);
  1100 }
  1101 
  1102 /* returns a new global reference which needs to be released later */
  1103 static jobject Android_JNI_GetSystemServiceObject(const char* name)
  1104 {
  1105     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  1106     JNIEnv* env = Android_JNI_GetEnv();
  1107     jobject retval = NULL;
  1108 
  1109     if (!LocalReferenceHolder_Init(&refs, env)) {
  1110         LocalReferenceHolder_Cleanup(&refs);
  1111         return NULL;
  1112     }
  1113 
  1114     jstring service = (*env)->NewStringUTF(env, name);
  1115 
  1116     jmethodID mid;
  1117 
  1118     mid = (*env)->GetStaticMethodID(env, mActivityClass, "getContext", "()Landroid/content/Context;");
  1119     jobject context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
  1120 
  1121     mid = (*env)->GetMethodID(env, mActivityClass, "getSystemServiceFromUiThread", "(Ljava/lang/String;)Ljava/lang/Object;");
  1122     jobject manager = (*env)->CallObjectMethod(env, context, mid, service);
  1123 
  1124     (*env)->DeleteLocalRef(env, service);
  1125 
  1126     retval = manager ? (*env)->NewGlobalRef(env, manager) : NULL;
  1127     LocalReferenceHolder_Cleanup(&refs);
  1128     return retval;
  1129 }
  1130 
  1131 #define SETUP_CLIPBOARD(error) \
  1132     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); \
  1133     JNIEnv* env = Android_JNI_GetEnv(); \
  1134     if (!LocalReferenceHolder_Init(&refs, env)) { \
  1135         LocalReferenceHolder_Cleanup(&refs); \
  1136         return error; \
  1137     } \
  1138     jobject clipboard = Android_JNI_GetSystemServiceObject("clipboard"); \
  1139     if (!clipboard) { \
  1140         LocalReferenceHolder_Cleanup(&refs); \
  1141         return error; \
  1142     }
  1143 
  1144 #define CLEANUP_CLIPBOARD() \
  1145     LocalReferenceHolder_Cleanup(&refs);
  1146 
  1147 int Android_JNI_SetClipboardText(const char* text)
  1148 {
  1149     SETUP_CLIPBOARD(-1)
  1150 
  1151     jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, clipboard), "setText", "(Ljava/lang/CharSequence;)V");
  1152     jstring string = (*env)->NewStringUTF(env, text);
  1153     (*env)->CallVoidMethod(env, clipboard, mid, string);
  1154     (*env)->DeleteGlobalRef(env, clipboard);
  1155     (*env)->DeleteLocalRef(env, string);
  1156 
  1157     CLEANUP_CLIPBOARD();
  1158 
  1159     return 0;
  1160 }
  1161 
  1162 char* Android_JNI_GetClipboardText(void)
  1163 {
  1164     SETUP_CLIPBOARD(SDL_strdup(""))
  1165 
  1166     jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, clipboard), "getText", "()Ljava/lang/CharSequence;");
  1167     jobject sequence = (*env)->CallObjectMethod(env, clipboard, mid);
  1168     (*env)->DeleteGlobalRef(env, clipboard);
  1169     if (sequence) {
  1170         mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, sequence), "toString", "()Ljava/lang/String;");
  1171         jstring string = (jstring)((*env)->CallObjectMethod(env, sequence, mid));
  1172         const char* utf = (*env)->GetStringUTFChars(env, string, 0);
  1173         if (utf) {
  1174             char* text = SDL_strdup(utf);
  1175             (*env)->ReleaseStringUTFChars(env, string, utf);
  1176 
  1177             CLEANUP_CLIPBOARD();
  1178 
  1179             return text;
  1180         }
  1181     }
  1182 
  1183     CLEANUP_CLIPBOARD();    
  1184 
  1185     return SDL_strdup("");
  1186 }
  1187 
  1188 SDL_bool Android_JNI_HasClipboardText(void)
  1189 {
  1190     SETUP_CLIPBOARD(SDL_FALSE)
  1191 
  1192     jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, clipboard), "hasText", "()Z");
  1193     jboolean has = (*env)->CallBooleanMethod(env, clipboard, mid);
  1194     (*env)->DeleteGlobalRef(env, clipboard);
  1195 
  1196     CLEANUP_CLIPBOARD();
  1197     
  1198     return has ? SDL_TRUE : SDL_FALSE;
  1199 }
  1200 
  1201 
  1202 /* returns 0 on success or -1 on error (others undefined then)
  1203  * returns truthy or falsy value in plugged, charged and battery
  1204  * returns the value in seconds and percent or -1 if not available
  1205  */
  1206 int Android_JNI_GetPowerInfo(int* plugged, int* charged, int* battery, int* seconds, int* percent)
  1207 {
  1208     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  1209     JNIEnv* env = Android_JNI_GetEnv();
  1210     if (!LocalReferenceHolder_Init(&refs, env)) {
  1211         LocalReferenceHolder_Cleanup(&refs);
  1212         return -1;
  1213     }
  1214 
  1215     jmethodID mid;
  1216 
  1217     mid = (*env)->GetStaticMethodID(env, mActivityClass, "getContext", "()Landroid/content/Context;");
  1218     jobject context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
  1219 
  1220     jstring action = (*env)->NewStringUTF(env, "android.intent.action.BATTERY_CHANGED");
  1221 
  1222     jclass cls = (*env)->FindClass(env, "android/content/IntentFilter");
  1223 
  1224     mid = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/lang/String;)V");
  1225     jobject filter = (*env)->NewObject(env, cls, mid, action);
  1226 
  1227     (*env)->DeleteLocalRef(env, action);
  1228 
  1229     mid = (*env)->GetMethodID(env, mActivityClass, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;");
  1230     jobject intent = (*env)->CallObjectMethod(env, context, mid, NULL, filter);
  1231 
  1232     (*env)->DeleteLocalRef(env, filter);
  1233 
  1234     cls = (*env)->GetObjectClass(env, intent);
  1235 
  1236     jstring iname;
  1237     jmethodID imid = (*env)->GetMethodID(env, cls, "getIntExtra", "(Ljava/lang/String;I)I");
  1238 
  1239 #define GET_INT_EXTRA(var, key) \
  1240     iname = (*env)->NewStringUTF(env, key); \
  1241     int var = (*env)->CallIntMethod(env, intent, imid, iname, -1); \
  1242     (*env)->DeleteLocalRef(env, iname);
  1243 
  1244     jstring bname;
  1245     jmethodID bmid = (*env)->GetMethodID(env, cls, "getBooleanExtra", "(Ljava/lang/String;Z)Z");
  1246 
  1247 #define GET_BOOL_EXTRA(var, key) \
  1248     bname = (*env)->NewStringUTF(env, key); \
  1249     int var = (*env)->CallBooleanMethod(env, intent, bmid, bname, JNI_FALSE); \
  1250     (*env)->DeleteLocalRef(env, bname);
  1251 
  1252     if (plugged) {
  1253         GET_INT_EXTRA(plug, "plugged") /* == BatteryManager.EXTRA_PLUGGED (API 5) */
  1254         if (plug == -1) {
  1255             LocalReferenceHolder_Cleanup(&refs);
  1256             return -1;
  1257         }
  1258         /* 1 == BatteryManager.BATTERY_PLUGGED_AC */
  1259         /* 2 == BatteryManager.BATTERY_PLUGGED_USB */
  1260         *plugged = (0 < plug) ? 1 : 0;
  1261     }
  1262 
  1263     if (charged) {
  1264         GET_INT_EXTRA(status, "status") /* == BatteryManager.EXTRA_STATUS (API 5) */
  1265         if (status == -1) {
  1266             LocalReferenceHolder_Cleanup(&refs);
  1267             return -1;
  1268         }
  1269         /* 5 == BatteryManager.BATTERY_STATUS_FULL */
  1270         *charged = (status == 5) ? 1 : 0;
  1271     }
  1272 
  1273     if (battery) {
  1274         GET_BOOL_EXTRA(present, "present") /* == BatteryManager.EXTRA_PRESENT (API 5) */
  1275         *battery = present ? 1 : 0;
  1276     }
  1277 
  1278     if (seconds) {
  1279         *seconds = -1; /* not possible */
  1280     }
  1281 
  1282     if (percent) {
  1283         GET_INT_EXTRA(level, "level") /* == BatteryManager.EXTRA_LEVEL (API 5) */
  1284         GET_INT_EXTRA(scale, "scale") /* == BatteryManager.EXTRA_SCALE (API 5) */
  1285         if ((level == -1) || (scale == -1)) {
  1286             LocalReferenceHolder_Cleanup(&refs);
  1287             return -1;
  1288         }
  1289         *percent = level * 100 / scale;
  1290     }
  1291 
  1292     (*env)->DeleteLocalRef(env, intent);
  1293 
  1294     LocalReferenceHolder_Cleanup(&refs);
  1295     return 0;
  1296 }
  1297 
  1298 /* returns number of found touch devices as return value and ids in parameter ids */
  1299 int Android_JNI_GetTouchDeviceIds(int **ids) {
  1300     JNIEnv *env = Android_JNI_GetEnv();
  1301     jint sources = 4098; /* == InputDevice.SOURCE_TOUCHSCREEN */
  1302     jmethodID mid = (*env)->GetStaticMethodID(env, mActivityClass, "inputGetInputDeviceIds", "(I)[I");
  1303     jintArray array = (jintArray) (*env)->CallStaticObjectMethod(env, mActivityClass, mid, sources);
  1304     int number = 0;
  1305     *ids = NULL;
  1306     if (array) {
  1307         number = (int) (*env)->GetArrayLength(env, array);
  1308         if (0 < number) {
  1309             jint* elements = (*env)->GetIntArrayElements(env, array, NULL);
  1310             if (elements) {
  1311                 int i;
  1312                 *ids = SDL_malloc(number * sizeof (**ids));
  1313                 for (i = 0; i < number; ++i) { /* not assuming sizeof (jint) == sizeof (int) */
  1314                     (*ids)[i] = elements[i];
  1315                 }
  1316                 (*env)->ReleaseIntArrayElements(env, array, elements, JNI_ABORT);
  1317             }
  1318         }
  1319         (*env)->DeleteLocalRef(env, array);
  1320     }
  1321     return number;
  1322 }
  1323 
  1324 void Android_JNI_PollInputDevices(void)
  1325 {
  1326     JNIEnv *env = Android_JNI_GetEnv();
  1327     (*env)->CallStaticVoidMethod(env, mActivityClass, midPollInputDevices);    
  1328 }
  1329 
  1330 /* See SDLActivity.java for constants. */
  1331 #define COMMAND_SET_KEEP_SCREEN_ON    5
  1332 
  1333 /* sends message to be handled on the UI event dispatch thread */
  1334 int Android_JNI_SendMessage(int command, int param)
  1335 {
  1336     JNIEnv *env = Android_JNI_GetEnv();
  1337     if (!env) {
  1338         return -1;
  1339     }
  1340     jmethodID mid = (*env)->GetStaticMethodID(env, mActivityClass, "sendMessage", "(II)Z");
  1341     if (!mid) {
  1342         return -1;
  1343     }
  1344     jboolean success = (*env)->CallStaticBooleanMethod(env, mActivityClass, mid, command, param);
  1345     return success ? 0 : -1;
  1346 }
  1347 
  1348 void Android_JNI_SuspendScreenSaver(SDL_bool suspend)
  1349 {
  1350     Android_JNI_SendMessage(COMMAND_SET_KEEP_SCREEN_ON, (suspend == SDL_FALSE) ? 0 : 1);
  1351 }
  1352 
  1353 void Android_JNI_ShowTextInput(SDL_Rect *inputRect)
  1354 {
  1355     JNIEnv *env = Android_JNI_GetEnv();
  1356     if (!env) {
  1357         return;
  1358     }
  1359 
  1360     jmethodID mid = (*env)->GetStaticMethodID(env, mActivityClass, "showTextInput", "(IIII)Z");
  1361     if (!mid) {
  1362         return;
  1363     }
  1364     (*env)->CallStaticBooleanMethod(env, mActivityClass, mid,
  1365                                inputRect->x,
  1366                                inputRect->y,
  1367                                inputRect->w,
  1368                                inputRect->h );
  1369 }
  1370 
  1371 void Android_JNI_HideTextInput(void)
  1372 {
  1373     /* has to match Activity constant */
  1374     const int COMMAND_TEXTEDIT_HIDE = 3;
  1375     Android_JNI_SendMessage(COMMAND_TEXTEDIT_HIDE, 0);
  1376 }
  1377 
  1378 int Android_JNI_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid)
  1379 {
  1380     JNIEnv *env;
  1381     jclass clazz;
  1382     jmethodID mid;
  1383     jobject context;
  1384     jstring title;
  1385     jstring message;
  1386     jintArray button_flags;
  1387     jintArray button_ids;
  1388     jobjectArray button_texts;
  1389     jintArray colors;
  1390     jobject text;
  1391     jint temp;
  1392     int i;
  1393 
  1394     env = Android_JNI_GetEnv();
  1395 
  1396     /* convert parameters */
  1397 
  1398     clazz = (*env)->FindClass(env, "java/lang/String");
  1399 
  1400     title = (*env)->NewStringUTF(env, messageboxdata->title);
  1401     message = (*env)->NewStringUTF(env, messageboxdata->message);
  1402 
  1403     button_flags = (*env)->NewIntArray(env, messageboxdata->numbuttons);
  1404     button_ids = (*env)->NewIntArray(env, messageboxdata->numbuttons);
  1405     button_texts = (*env)->NewObjectArray(env, messageboxdata->numbuttons,
  1406         clazz, NULL);
  1407     for (i = 0; i < messageboxdata->numbuttons; ++i) {
  1408         temp = messageboxdata->buttons[i].flags;
  1409         (*env)->SetIntArrayRegion(env, button_flags, i, 1, &temp);
  1410         temp = messageboxdata->buttons[i].buttonid;
  1411         (*env)->SetIntArrayRegion(env, button_ids, i, 1, &temp);
  1412         text = (*env)->NewStringUTF(env, messageboxdata->buttons[i].text);
  1413         (*env)->SetObjectArrayElement(env, button_texts, i, text);
  1414         (*env)->DeleteLocalRef(env, text);
  1415     }
  1416 
  1417     if (messageboxdata->colorScheme) {
  1418         colors = (*env)->NewIntArray(env, SDL_MESSAGEBOX_COLOR_MAX);
  1419         for (i = 0; i < SDL_MESSAGEBOX_COLOR_MAX; ++i) {
  1420             temp = (0xFF << 24) |
  1421                    (messageboxdata->colorScheme->colors[i].r << 16) |
  1422                    (messageboxdata->colorScheme->colors[i].g << 8) |
  1423                    (messageboxdata->colorScheme->colors[i].b << 0);
  1424             (*env)->SetIntArrayRegion(env, colors, i, 1, &temp);
  1425         }
  1426     } else {
  1427         colors = NULL;
  1428     }
  1429 
  1430     (*env)->DeleteLocalRef(env, clazz);
  1431 
  1432     /* call function */
  1433 
  1434     mid = (*env)->GetStaticMethodID(env, mActivityClass, "getContext","()Landroid/content/Context;");
  1435 
  1436     context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
  1437 
  1438     clazz = (*env)->GetObjectClass(env, context);
  1439 
  1440     mid = (*env)->GetMethodID(env, clazz,
  1441         "messageboxShowMessageBox", "(ILjava/lang/String;Ljava/lang/String;[I[I[Ljava/lang/String;[I)I");
  1442     *buttonid = (*env)->CallIntMethod(env, context, mid,
  1443         messageboxdata->flags,
  1444         title,
  1445         message,
  1446         button_flags,
  1447         button_ids,
  1448         button_texts,
  1449         colors);
  1450 
  1451     (*env)->DeleteLocalRef(env, context);
  1452     (*env)->DeleteLocalRef(env, clazz);
  1453 
  1454     /* delete parameters */
  1455 
  1456     (*env)->DeleteLocalRef(env, title);
  1457     (*env)->DeleteLocalRef(env, message);
  1458     (*env)->DeleteLocalRef(env, button_flags);
  1459     (*env)->DeleteLocalRef(env, button_ids);
  1460     (*env)->DeleteLocalRef(env, button_texts);
  1461     (*env)->DeleteLocalRef(env, colors);
  1462 
  1463     return 0;
  1464 }
  1465 
  1466 /*
  1467 //////////////////////////////////////////////////////////////////////////////
  1468 //
  1469 // Functions exposed to SDL applications in SDL_system.h
  1470 //////////////////////////////////////////////////////////////////////////////
  1471 */
  1472 
  1473 void *SDL_AndroidGetJNIEnv()
  1474 {
  1475     return Android_JNI_GetEnv();
  1476 }
  1477 
  1478 
  1479 
  1480 void *SDL_AndroidGetActivity()
  1481 {
  1482     /* See SDL_system.h for caveats on using this function. */
  1483 
  1484     jmethodID mid;
  1485 
  1486     JNIEnv *env = Android_JNI_GetEnv();
  1487     if (!env) {
  1488         return NULL;
  1489     }
  1490 
  1491     /* return SDLActivity.getContext(); */
  1492     mid = (*env)->GetStaticMethodID(env, mActivityClass,
  1493             "getContext","()Landroid/content/Context;");
  1494     return (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
  1495 }
  1496 
  1497 const char * SDL_AndroidGetInternalStoragePath()
  1498 {
  1499     static char *s_AndroidInternalFilesPath = NULL;
  1500 
  1501     if (!s_AndroidInternalFilesPath) {
  1502         struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  1503         jmethodID mid;
  1504         jobject context;
  1505         jobject fileObject;
  1506         jstring pathString;
  1507         const char *path;
  1508 
  1509         JNIEnv *env = Android_JNI_GetEnv();
  1510         if (!LocalReferenceHolder_Init(&refs, env)) {
  1511             LocalReferenceHolder_Cleanup(&refs);
  1512             return NULL;
  1513         }
  1514 
  1515         /* context = SDLActivity.getContext(); */
  1516         mid = (*env)->GetStaticMethodID(env, mActivityClass,
  1517                 "getContext","()Landroid/content/Context;");
  1518         context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
  1519 
  1520         /* fileObj = context.getFilesDir(); */
  1521         mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
  1522                 "getFilesDir", "()Ljava/io/File;");
  1523         fileObject = (*env)->CallObjectMethod(env, context, mid);
  1524         if (!fileObject) {
  1525             SDL_SetError("Couldn't get internal directory");
  1526             LocalReferenceHolder_Cleanup(&refs);
  1527             return NULL;
  1528         }
  1529 
  1530         /* path = fileObject.getAbsolutePath(); */
  1531         mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject),
  1532                 "getAbsolutePath", "()Ljava/lang/String;");
  1533         pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid);
  1534 
  1535         path = (*env)->GetStringUTFChars(env, pathString, NULL);
  1536         s_AndroidInternalFilesPath = SDL_strdup(path);
  1537         (*env)->ReleaseStringUTFChars(env, pathString, path);
  1538 
  1539         LocalReferenceHolder_Cleanup(&refs);
  1540     }
  1541     return s_AndroidInternalFilesPath;
  1542 }
  1543 
  1544 int SDL_AndroidGetExternalStorageState()
  1545 {
  1546     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  1547     jmethodID mid;
  1548     jclass cls;
  1549     jstring stateString;
  1550     const char *state;
  1551     int stateFlags;
  1552 
  1553     JNIEnv *env = Android_JNI_GetEnv();
  1554     if (!LocalReferenceHolder_Init(&refs, env)) {
  1555         LocalReferenceHolder_Cleanup(&refs);
  1556         return 0;
  1557     }
  1558 
  1559     cls = (*env)->FindClass(env, "android/os/Environment");
  1560     mid = (*env)->GetStaticMethodID(env, cls,
  1561             "getExternalStorageState", "()Ljava/lang/String;");
  1562     stateString = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid);
  1563 
  1564     state = (*env)->GetStringUTFChars(env, stateString, NULL);
  1565 
  1566     /* Print an info message so people debugging know the storage state */
  1567     __android_log_print(ANDROID_LOG_INFO, "SDL", "external storage state: %s", state);
  1568 
  1569     if (SDL_strcmp(state, "mounted") == 0) {
  1570         stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ |
  1571                      SDL_ANDROID_EXTERNAL_STORAGE_WRITE;
  1572     } else if (SDL_strcmp(state, "mounted_ro") == 0) {
  1573         stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ;
  1574     } else {
  1575         stateFlags = 0;
  1576     }
  1577     (*env)->ReleaseStringUTFChars(env, stateString, state);
  1578 
  1579     LocalReferenceHolder_Cleanup(&refs);
  1580     return stateFlags;
  1581 }
  1582 
  1583 const char * SDL_AndroidGetExternalStoragePath()
  1584 {
  1585     static char *s_AndroidExternalFilesPath = NULL;
  1586 
  1587     if (!s_AndroidExternalFilesPath) {
  1588         struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  1589         jmethodID mid;
  1590         jobject context;
  1591         jobject fileObject;
  1592         jstring pathString;
  1593         const char *path;
  1594 
  1595         JNIEnv *env = Android_JNI_GetEnv();
  1596         if (!LocalReferenceHolder_Init(&refs, env)) {
  1597             LocalReferenceHolder_Cleanup(&refs);
  1598             return NULL;
  1599         }
  1600 
  1601         /* context = SDLActivity.getContext(); */
  1602         mid = (*env)->GetStaticMethodID(env, mActivityClass,
  1603                 "getContext","()Landroid/content/Context;");
  1604         context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
  1605 
  1606         /* fileObj = context.getExternalFilesDir(); */
  1607         mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
  1608                 "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;");
  1609         fileObject = (*env)->CallObjectMethod(env, context, mid, NULL);
  1610         if (!fileObject) {
  1611             SDL_SetError("Couldn't get external directory");
  1612             LocalReferenceHolder_Cleanup(&refs);
  1613             return NULL;
  1614         }
  1615 
  1616         /* path = fileObject.getAbsolutePath(); */
  1617         mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject),
  1618                 "getAbsolutePath", "()Ljava/lang/String;");
  1619         pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid);
  1620 
  1621         path = (*env)->GetStringUTFChars(env, pathString, NULL);
  1622         s_AndroidExternalFilesPath = SDL_strdup(path);
  1623         (*env)->ReleaseStringUTFChars(env, pathString, path);
  1624 
  1625         LocalReferenceHolder_Cleanup(&refs);
  1626     }
  1627     return s_AndroidExternalFilesPath;
  1628 }
  1629 
  1630 jclass Android_JNI_GetActivityClass(void)
  1631 {
  1632     return mActivityClass;
  1633 }
  1634 
  1635 #endif /* __ANDROID__ */
  1636 
  1637 /* vi: set ts=4 sw=4 expandtab: */
  1638