src/core/android/SDL_android.c
author Philipp Wiesemann <philipp.wiesemann@arcor.de>
Fri, 23 Jan 2015 20:29:08 +0100
changeset 9314 8d826bc39a45
parent 9271 8f71cb5dc099
child 9322 a45110bca65e
permissions -rw-r--r--
Fixed bug 2816 - [patch] Android: Expose screen refresh rate

Jonas Kulla

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