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