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