src/core/android/SDL_android.c
author Philipp Wiesemann <philipp.wiesemann@arcor.de>
Thu, 17 Sep 2015 22:24:54 +0200
changeset 9869 4ba43d626c4a
parent 9858 6d6a972746b3
child 9870 6dd5ab47534b
permissions -rw-r--r--
Android: Renamed SDLActivity's Java method used for APK expansion files.

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