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