src/core/android/SDL_android.c
author Sam Lantinga <slouken@libsdl.org>
Tue, 24 Oct 2017 00:17:07 -0700
changeset 11653 4130b92b6be4
parent 11531 84793b17a0d2
child 11655 ccf47d584003
permissions -rw-r--r--
Added the ability to set SDL hints from AndroidManifest.xml (thanks Rachel!)
This is especially useful for things like the accelerometer hint which could be needed before application main().
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2017 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 #include "SDL_main.h"
    27 
    28 #ifdef __ANDROID__
    29 
    30 #include "SDL_system.h"
    31 #include "SDL_android.h"
    32 #include <EGL/egl.h>
    33 
    34 #include "../../events/SDL_events_c.h"
    35 #include "../../video/android/SDL_androidkeyboard.h"
    36 #include "../../video/android/SDL_androidmouse.h"
    37 #include "../../video/android/SDL_androidtouch.h"
    38 #include "../../video/android/SDL_androidvideo.h"
    39 #include "../../video/android/SDL_androidwindow.h"
    40 #include "../../joystick/android/SDL_sysjoystick_c.h"
    41 #include "../../haptic/android/SDL_syshaptic_c.h"
    42 
    43 #include <android/log.h>
    44 #include <pthread.h>
    45 #include <sys/types.h>
    46 #include <unistd.h>
    47 #include <dlfcn.h>
    48 /* #define LOG_TAG "SDL_android" */
    49 /* #define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) */
    50 /* #define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) */
    51 #define LOGI(...) do {} while (0)
    52 #define LOGE(...) do {} while (0)
    53 
    54 
    55 #define SDL_JAVA_PREFIX                                 org_libsdl_app
    56 #define CONCAT1(prefix, class, function)                CONCAT2(prefix, class, function)
    57 #define CONCAT2(prefix, class, function)                Java_ ## prefix ## _ ## class ## _ ## function
    58 #define SDL_JAVA_INTERFACE(function)                    CONCAT1(SDL_JAVA_PREFIX, SDLActivity, function)
    59 #define SDL_JAVA_AUDIO_INTERFACE(function)              CONCAT1(SDL_JAVA_PREFIX, SDLAudioManager, function)
    60 #define SDL_JAVA_CONTROLLER_INTERFACE(function)         CONCAT1(SDL_JAVA_PREFIX, SDLControllerManager, function)
    61 #define SDL_JAVA_INTERFACE_INPUT_CONNECTION(function)   CONCAT1(SDL_JAVA_PREFIX, SDLInputConnection, function)
    62 
    63 
    64 /* Java class SDLActivity */
    65 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(
    66         JNIEnv* mEnv, jclass cls);
    67 
    68 JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeRunMain)(
    69         JNIEnv* env, jclass cls,
    70         jstring library, jstring function, jobject array);
    71 
    72 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDropFile)(
    73         JNIEnv* env, jclass jcls,
    74         jstring filename);
    75 
    76 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeResize)(
    77         JNIEnv* env, jclass jcls,
    78         jint width, jint height, jint format, jfloat rate);
    79 
    80 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceChanged)(
    81         JNIEnv* env, jclass jcls);
    82 
    83 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceDestroyed)(
    84         JNIEnv* env, jclass jcls);
    85 
    86 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyDown)(
    87         JNIEnv* env, jclass jcls,
    88         jint keycode);
    89 
    90 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyUp)(
    91         JNIEnv* env, jclass jcls,
    92         jint keycode);
    93 
    94 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyboardFocusLost)(
    95         JNIEnv* env, jclass jcls);
    96 
    97 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeTouch)(
    98         JNIEnv* env, jclass jcls,
    99         jint touch_device_id_in, jint pointer_finger_id_in,
   100         jint action, jfloat x, jfloat y, jfloat p);
   101 
   102 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeMouse)(
   103         JNIEnv* env, jclass jcls,
   104         jint button, jint action, jfloat x, jfloat y);
   105 
   106 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeAccel)(
   107         JNIEnv* env, jclass jcls,
   108         jfloat x, jfloat y, jfloat z);
   109 
   110 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeClipboardChanged)(
   111         JNIEnv* env, jclass jcls);
   112 
   113 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeLowMemory)(
   114         JNIEnv* env, jclass cls);
   115 
   116 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeQuit)(
   117         JNIEnv* env, jclass cls);
   118 
   119 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativePause)(
   120         JNIEnv* env, jclass cls);
   121 
   122 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeResume)(
   123         JNIEnv* env, jclass cls);
   124 
   125 JNIEXPORT jstring JNICALL SDL_JAVA_INTERFACE(nativeGetHint)(
   126         JNIEnv* env, jclass cls,
   127         jstring name);
   128 
   129 /* Java class SDLInputConnection */
   130 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeCommitText)(
   131         JNIEnv* env, jclass cls,
   132         jstring text, jint newCursorPosition);
   133 
   134 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeSetComposingText)(
   135         JNIEnv* env, jclass cls,
   136         jstring text, jint newCursorPosition);
   137 
   138 /* Java class SDLAudioManager */
   139 JNIEXPORT void JNICALL SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI)(
   140         JNIEnv *env, jclass jcls);
   141 
   142 /* Java class SDLControllerManager */
   143 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI)(
   144         JNIEnv *env, jclass jcls);
   145 
   146 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadDown)(
   147         JNIEnv* env, jclass jcls,
   148         jint device_id, jint keycode);
   149 
   150 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadUp)(
   151         JNIEnv* env, jclass jcls,
   152         jint device_id, jint keycode);
   153 
   154 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeJoy)(
   155         JNIEnv* env, jclass jcls,
   156         jint device_id, jint axis, jfloat value);
   157 
   158 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeHat)(
   159         JNIEnv* env, jclass jcls,
   160         jint device_id, jint hat_id, jint x, jint y);
   161 
   162 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick)(
   163         JNIEnv* env, jclass jcls,
   164         jint device_id, jstring device_name, jstring device_desc, jint is_accelerometer,
   165         jint nbuttons, jint naxes, jint nhats, jint nballs);
   166 
   167 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveJoystick)(
   168         JNIEnv* env, jclass jcls,
   169         jint device_id);
   170 
   171 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddHaptic)(
   172         JNIEnv* env, jclass jcls,
   173         jint device_id, jstring device_name);
   174 
   175 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveHaptic)(
   176         JNIEnv* env, jclass jcls,
   177         jint device_id);
   178 
   179 
   180 
   181 /* Uncomment this to log messages entering and exiting methods in this file */
   182 /* #define DEBUG_JNI */
   183 
   184 static void Android_JNI_ThreadDestroyed(void*);
   185 
   186 /*******************************************************************************
   187  This file links the Java side of Android with libsdl
   188 *******************************************************************************/
   189 #include <jni.h>
   190 
   191 
   192 /*******************************************************************************
   193                                Globals
   194 *******************************************************************************/
   195 static pthread_key_t mThreadKey;
   196 static JavaVM* mJavaVM;
   197 
   198 /* Main activity */
   199 static jclass mActivityClass;
   200 
   201 /* method signatures */
   202 static jmethodID midGetNativeSurface;
   203 static jmethodID midSetActivityTitle;
   204 static jmethodID midSetOrientation;
   205 static jmethodID midGetContext;
   206 static jmethodID midInputGetInputDeviceIds;
   207 static jmethodID midSendMessage;
   208 static jmethodID midShowTextInput;
   209 static jmethodID midIsScreenKeyboardShown;
   210 static jmethodID midClipboardSetText;
   211 static jmethodID midClipboardGetText;
   212 static jmethodID midClipboardHasText;
   213 static jmethodID midOpenAPKExpansionInputStream;
   214 static jmethodID midGetManifestEnvironmentVariable;
   215 
   216 /* audio manager */
   217 static jclass mAudioManagerClass;
   218 
   219 /* method signatures */
   220 static jmethodID midAudioOpen;
   221 static jmethodID midAudioWriteShortBuffer;
   222 static jmethodID midAudioWriteByteBuffer;
   223 static jmethodID midAudioClose;
   224 static jmethodID midCaptureOpen;
   225 static jmethodID midCaptureReadShortBuffer;
   226 static jmethodID midCaptureReadByteBuffer;
   227 static jmethodID midCaptureClose;
   228 
   229 /* controller manager */
   230 static jclass mControllerManagerClass;
   231 
   232 /* method signatures */
   233 static jmethodID midPollInputDevices;
   234 static jmethodID midPollHapticDevices;
   235 static jmethodID midHapticRun;
   236 
   237 /* static fields */
   238 static jfieldID fidSeparateMouseAndTouch;
   239 
   240 /* Accelerometer data storage */
   241 static float fLastAccelerometer[3];
   242 static SDL_bool bHasNewData;
   243 
   244 /*******************************************************************************
   245                  Functions called by JNI
   246 *******************************************************************************/
   247 
   248 /* Library init */
   249 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
   250 {
   251     JNIEnv *env;
   252     mJavaVM = vm;
   253     LOGI("JNI_OnLoad called");
   254     if ((*mJavaVM)->GetEnv(mJavaVM, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
   255         LOGE("Failed to get the environment using GetEnv()");
   256         return -1;
   257     }
   258     /*
   259      * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread
   260      * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this
   261      */
   262     if (pthread_key_create(&mThreadKey, Android_JNI_ThreadDestroyed) != 0) {
   263         __android_log_print(ANDROID_LOG_ERROR, "SDL", "Error initializing pthread key");
   264     }
   265     Android_JNI_SetupThread();
   266 
   267     return JNI_VERSION_1_4;
   268 }
   269 
   270 void checkJNIReady()
   271 {
   272     if (!mActivityClass || !mAudioManagerClass || !mControllerManagerClass) {
   273         // We aren't fully initialized, let's just return.
   274         return;
   275     }
   276 
   277     SDL_SetMainReady();    
   278 }
   279 
   280 /* Activity initialization -- called before SDL_main() to initialize JNI bindings */
   281 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv* mEnv, jclass cls)
   282 {
   283     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeSetupJNI()");
   284 
   285     Android_JNI_SetupThread();
   286 
   287     mActivityClass = (jclass)((*mEnv)->NewGlobalRef(mEnv, cls));
   288 
   289     midGetNativeSurface = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
   290                                 "getNativeSurface","()Landroid/view/Surface;");
   291     midSetActivityTitle = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
   292                                 "setActivityTitle","(Ljava/lang/String;)Z");
   293     midSetOrientation = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
   294                                 "setOrientation","(IIZLjava/lang/String;)V");
   295     midGetContext = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
   296                                 "getContext","()Landroid/content/Context;");
   297     midInputGetInputDeviceIds = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
   298                                 "inputGetInputDeviceIds", "(I)[I");
   299     midSendMessage = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
   300                                 "sendMessage", "(II)Z");
   301     midShowTextInput =  (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
   302                                 "showTextInput", "(IIII)Z");
   303     midIsScreenKeyboardShown = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
   304                                 "isScreenKeyboardShown","()Z");
   305     midClipboardSetText = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
   306                                 "clipboardSetText", "(Ljava/lang/String;)V");
   307     midClipboardGetText = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
   308                                 "clipboardGetText", "()Ljava/lang/String;");
   309     midClipboardHasText = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
   310                                 "clipboardHasText", "()Z");
   311     midOpenAPKExpansionInputStream = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
   312                                 "openAPKExpansionInputStream", "(Ljava/lang/String;)Ljava/io/InputStream;");
   313 
   314     midGetManifestEnvironmentVariable = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
   315                                 "getManifestEnvironmentVariable", "(Ljava/lang/String;)Ljava/lang/String;");
   316 
   317     if (!midGetNativeSurface ||
   318        !midSetActivityTitle || !midSetOrientation || !midGetContext || !midInputGetInputDeviceIds ||
   319        !midSendMessage || !midShowTextInput || !midIsScreenKeyboardShown || 
   320        !midClipboardSetText || !midClipboardGetText || !midClipboardHasText ||
   321        !midOpenAPKExpansionInputStream || !midGetManifestEnvironmentVariable) {
   322         __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLActivity.java?");
   323     }
   324 
   325     fidSeparateMouseAndTouch = (*mEnv)->GetStaticFieldID(mEnv, mActivityClass, "mSeparateMouseAndTouch", "Z");
   326 
   327     if (!fidSeparateMouseAndTouch) {
   328         __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java static fields, do you have the latest version of SDLActivity.java?");
   329     }
   330 
   331     checkJNIReady();
   332 }
   333 
   334 /* Audio initialization -- called before SDL_main() to initialize JNI bindings */
   335 JNIEXPORT void JNICALL SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI)(JNIEnv* mEnv, jclass cls)
   336 {
   337     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "AUDIO nativeSetupJNI()");
   338 
   339     Android_JNI_SetupThread();
   340 
   341     mAudioManagerClass = (jclass)((*mEnv)->NewGlobalRef(mEnv, cls));
   342 
   343     midAudioOpen = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
   344                                 "audioOpen", "(IZZI)I");
   345     midAudioWriteShortBuffer = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
   346                                 "audioWriteShortBuffer", "([S)V");
   347     midAudioWriteByteBuffer = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
   348                                 "audioWriteByteBuffer", "([B)V");
   349     midAudioClose = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
   350                                 "audioClose", "()V");
   351     midCaptureOpen = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
   352                                 "captureOpen", "(IZZI)I");
   353     midCaptureReadShortBuffer = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
   354                                 "captureReadShortBuffer", "([SZ)I");
   355     midCaptureReadByteBuffer = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
   356                                 "captureReadByteBuffer", "([BZ)I");
   357     midCaptureClose = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
   358                                 "captureClose", "()V");
   359 
   360     if (!midAudioOpen || !midAudioWriteShortBuffer || !midAudioWriteByteBuffer || !midAudioClose ||
   361        !midCaptureOpen || !midCaptureReadShortBuffer || !midCaptureReadByteBuffer || !midCaptureClose) {
   362         __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLAudioManager.java?");
   363     }
   364 
   365     checkJNIReady();
   366 }
   367 
   368 /* Controller initialization -- called before SDL_main() to initialize JNI bindings */
   369 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI)(JNIEnv* mEnv, jclass cls)
   370 {
   371     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "CONTROLLER nativeSetupJNI()");
   372 
   373     Android_JNI_SetupThread();
   374 
   375     mControllerManagerClass = (jclass)((*mEnv)->NewGlobalRef(mEnv, cls));
   376 
   377     midPollInputDevices = (*mEnv)->GetStaticMethodID(mEnv, mControllerManagerClass,
   378                                 "pollInputDevices", "()V");
   379     midPollHapticDevices = (*mEnv)->GetStaticMethodID(mEnv, mControllerManagerClass,
   380                                 "pollHapticDevices", "()V");
   381     midHapticRun = (*mEnv)->GetStaticMethodID(mEnv, mControllerManagerClass,
   382                                 "hapticRun", "(II)V");
   383 
   384     if (!midPollInputDevices || !midPollHapticDevices || !midHapticRun) {
   385         __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLControllerManager.java?");
   386     }
   387 
   388     checkJNIReady();
   389 }
   390 
   391 /* SDL main function prototype */
   392 typedef int (*SDL_main_func)(int argc, char *argv[]);
   393 
   394 /* Start up the SDL app */
   395 JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeRunMain)(JNIEnv* env, jclass cls, jstring library, jstring function, jobject array)
   396 {
   397     int status = -1;
   398     const char *library_file;
   399     void *library_handle;
   400 
   401     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeRunMain()");
   402 
   403     library_file = (*env)->GetStringUTFChars(env, library, NULL);
   404     library_handle = dlopen(library_file, RTLD_GLOBAL);
   405     if (library_handle) {
   406         const char *function_name;
   407         SDL_main_func SDL_main;
   408 
   409         function_name = (*env)->GetStringUTFChars(env, function, NULL);
   410         SDL_main = (SDL_main_func)dlsym(library_handle, function_name);
   411         if (SDL_main) {
   412             int i;
   413             int argc;
   414             int len;
   415             char **argv;
   416 
   417             /* Prepare the arguments. */
   418             len = (*env)->GetArrayLength(env, array);
   419             argv = SDL_stack_alloc(char*, 1 + len + 1);
   420             argc = 0;
   421             /* Use the name "app_process" so PHYSFS_platformCalcBaseDir() works.
   422                https://bitbucket.org/MartinFelis/love-android-sdl2/issue/23/release-build-crash-on-start
   423              */
   424             argv[argc++] = SDL_strdup("app_process");
   425             for (i = 0; i < len; ++i) {
   426                 const char* utf;
   427                 char* arg = NULL;
   428                 jstring string = (*env)->GetObjectArrayElement(env, array, i);
   429                 if (string) {
   430                     utf = (*env)->GetStringUTFChars(env, string, 0);
   431                     if (utf) {
   432                         arg = SDL_strdup(utf);
   433                         (*env)->ReleaseStringUTFChars(env, string, utf);
   434                     }
   435                     (*env)->DeleteLocalRef(env, string);
   436                 }
   437                 if (!arg) {
   438                     arg = SDL_strdup("");
   439                 }
   440                 argv[argc++] = arg;
   441             }
   442             argv[argc] = NULL;
   443 
   444 
   445             /* Run the application. */
   446             status = SDL_main(argc, argv);
   447 
   448             /* Release the arguments. */
   449             for (i = 0; i < argc; ++i) {
   450                 SDL_free(argv[i]);
   451             }
   452             SDL_stack_free(argv);
   453 
   454         } else {
   455             __android_log_print(ANDROID_LOG_ERROR, "SDL", "nativeRunMain(): Couldn't find function %s in library %s", function_name, library_file);
   456         }
   457         (*env)->ReleaseStringUTFChars(env, function, function_name);
   458 
   459         dlclose(library_handle);
   460 
   461     } else {
   462         __android_log_print(ANDROID_LOG_ERROR, "SDL", "nativeRunMain(): Couldn't load library %s", library_file);
   463     }
   464     (*env)->ReleaseStringUTFChars(env, library, library_file);
   465 
   466     /* Do not issue an exit or the whole application will terminate instead of just the SDL thread */
   467     /* exit(status); */
   468 
   469     return status;
   470 }
   471 
   472 /* Drop file */
   473 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDropFile)(
   474                                     JNIEnv* env, jclass jcls,
   475                                     jstring filename)
   476 {
   477     const char *path = (*env)->GetStringUTFChars(env, filename, NULL);
   478     SDL_SendDropFile(NULL, path);
   479     (*env)->ReleaseStringUTFChars(env, filename, path);
   480     SDL_SendDropComplete(NULL);
   481 }
   482 
   483 /* Resize */
   484 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeResize)(
   485                                     JNIEnv* env, jclass jcls,
   486                                     jint width, jint height, jint format, jfloat rate)
   487 {
   488     Android_SetScreenResolution(width, height, format, rate);
   489 }
   490 
   491 /* Paddown */
   492 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadDown)(
   493                                     JNIEnv* env, jclass jcls,
   494                                     jint device_id, jint keycode)
   495 {
   496     return Android_OnPadDown(device_id, keycode);
   497 }
   498 
   499 /* Padup */
   500 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadUp)(
   501                                     JNIEnv* env, jclass jcls,
   502                                     jint device_id, jint keycode)
   503 {
   504     return Android_OnPadUp(device_id, keycode);
   505 }
   506 
   507 /* Joy */
   508 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeJoy)(
   509                                     JNIEnv* env, jclass jcls,
   510                                     jint device_id, jint axis, jfloat value)
   511 {
   512     Android_OnJoy(device_id, axis, value);
   513 }
   514 
   515 /* POV Hat */
   516 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeHat)(
   517                                     JNIEnv* env, jclass jcls,
   518                                     jint device_id, jint hat_id, jint x, jint y)
   519 {
   520     Android_OnHat(device_id, hat_id, x, y);
   521 }
   522 
   523 
   524 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick)(
   525                                     JNIEnv* env, jclass jcls,
   526                                     jint device_id, jstring device_name, jstring device_desc, jint is_accelerometer,
   527                                     jint nbuttons, jint naxes, jint nhats, jint nballs)
   528 {
   529     int retval;
   530     const char *name = (*env)->GetStringUTFChars(env, device_name, NULL);
   531     const char *desc = (*env)->GetStringUTFChars(env, device_desc, NULL);
   532 
   533     retval = Android_AddJoystick(device_id, name, desc, (SDL_bool) is_accelerometer, nbuttons, naxes, nhats, nballs);
   534 
   535     (*env)->ReleaseStringUTFChars(env, device_name, name);
   536     (*env)->ReleaseStringUTFChars(env, device_desc, desc);
   537 
   538     return retval;
   539 }
   540 
   541 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveJoystick)(
   542                                     JNIEnv* env, jclass jcls,
   543                                     jint device_id)
   544 {
   545     return Android_RemoveJoystick(device_id);
   546 }
   547 
   548 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddHaptic)(
   549     JNIEnv* env, jclass jcls, jint device_id, jstring device_name)
   550 {
   551     int retval;
   552     const char *name = (*env)->GetStringUTFChars(env, device_name, NULL);
   553 
   554     retval = Android_AddHaptic(device_id, name);
   555 
   556     (*env)->ReleaseStringUTFChars(env, device_name, name);
   557 
   558     return retval;
   559 }
   560 
   561 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveHaptic)(
   562     JNIEnv* env, jclass jcls, jint device_id)
   563 {
   564     return Android_RemoveHaptic(device_id);
   565 }
   566 
   567 
   568 /* Surface Created */
   569 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceChanged)(JNIEnv* env, jclass jcls)
   570 {
   571     SDL_WindowData *data;
   572     SDL_VideoDevice *_this;
   573 
   574     if (Android_Window == NULL || Android_Window->driverdata == NULL ) {
   575         return;
   576     }
   577 
   578     _this =  SDL_GetVideoDevice();
   579     data =  (SDL_WindowData *) Android_Window->driverdata;
   580 
   581     /* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */
   582     if (data->egl_surface == EGL_NO_SURFACE) {
   583         if(data->native_window) {
   584             ANativeWindow_release(data->native_window);
   585         }
   586         data->native_window = Android_JNI_GetNativeWindow();
   587         data->egl_surface = SDL_EGL_CreateSurface(_this, (NativeWindowType) data->native_window);
   588     }
   589 
   590     /* GL Context handling is done in the event loop because this function is run from the Java thread */
   591 
   592 }
   593 
   594 /* Surface Destroyed */
   595 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceDestroyed)(JNIEnv* env, jclass jcls)
   596 {
   597     /* We have to clear the current context and destroy the egl surface here
   598      * Otherwise there's BAD_NATIVE_WINDOW errors coming from eglCreateWindowSurface on resume
   599      * Ref: http://stackoverflow.com/questions/8762589/eglcreatewindowsurface-on-ics-and-switching-from-2d-to-3d
   600      */
   601     SDL_WindowData *data;
   602     SDL_VideoDevice *_this;
   603 
   604     if (Android_Window == NULL || Android_Window->driverdata == NULL ) {
   605         return;
   606     }
   607 
   608     _this =  SDL_GetVideoDevice();
   609     data = (SDL_WindowData *) Android_Window->driverdata;
   610 
   611     if (data->egl_surface != EGL_NO_SURFACE) {
   612         SDL_EGL_MakeCurrent(_this, NULL, NULL);
   613         SDL_EGL_DestroySurface(_this, data->egl_surface);
   614         data->egl_surface = EGL_NO_SURFACE;
   615     }
   616 
   617     /* GL Context handling is done in the event loop because this function is run from the Java thread */
   618 
   619 }
   620 
   621 /* Keydown */
   622 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyDown)(
   623                                     JNIEnv* env, jclass jcls,
   624                                     jint keycode)
   625 {
   626     Android_OnKeyDown(keycode);
   627 }
   628 
   629 /* Keyup */
   630 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyUp)(
   631                                     JNIEnv* env, jclass jcls,
   632                                     jint keycode)
   633 {
   634     Android_OnKeyUp(keycode);
   635 }
   636 
   637 /* Keyboard Focus Lost */
   638 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyboardFocusLost)(
   639                                     JNIEnv* env, jclass jcls)
   640 {
   641     /* Calling SDL_StopTextInput will take care of hiding the keyboard and cleaning up the DummyText widget */
   642     SDL_StopTextInput();
   643 }
   644 
   645 
   646 /* Touch */
   647 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeTouch)(
   648                                     JNIEnv* env, jclass jcls,
   649                                     jint touch_device_id_in, jint pointer_finger_id_in,
   650                                     jint action, jfloat x, jfloat y, jfloat p)
   651 {
   652     Android_OnTouch(touch_device_id_in, pointer_finger_id_in, action, x, y, p);
   653 }
   654 
   655 /* Mouse */
   656 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeMouse)(
   657                                     JNIEnv* env, jclass jcls,
   658                                     jint button, jint action, jfloat x, jfloat y)
   659 {
   660     Android_OnMouse(button, action, x, y);
   661 }
   662 
   663 /* Accelerometer */
   664 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeAccel)(
   665                                     JNIEnv* env, jclass jcls,
   666                                     jfloat x, jfloat y, jfloat z)
   667 {
   668     fLastAccelerometer[0] = x;
   669     fLastAccelerometer[1] = y;
   670     fLastAccelerometer[2] = z;
   671     bHasNewData = SDL_TRUE;
   672 }
   673 
   674 /* Clipboard */
   675 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeClipboardChanged)(
   676                                     JNIEnv* env, jclass jcls)
   677 {
   678     SDL_SendClipboardUpdate();
   679 }
   680 
   681 /* Low memory */
   682 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeLowMemory)(
   683                                     JNIEnv* env, jclass cls)
   684 {
   685     SDL_SendAppEvent(SDL_APP_LOWMEMORY);
   686 }
   687 
   688 /* Quit */
   689 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeQuit)(
   690                                     JNIEnv* env, jclass cls)
   691 {
   692     /* Discard previous events. The user should have handled state storage
   693      * in SDL_APP_WILLENTERBACKGROUND. After nativeQuit() is called, no
   694      * events other than SDL_QUIT and SDL_APP_TERMINATING should fire */
   695     SDL_FlushEvents(SDL_FIRSTEVENT, SDL_LASTEVENT);
   696     /* Inject a SDL_QUIT event */
   697     SDL_SendQuit();
   698     SDL_SendAppEvent(SDL_APP_TERMINATING);
   699     /* Resume the event loop so that the app can catch SDL_QUIT which
   700      * should now be the top event in the event queue. */
   701     if (!SDL_SemValue(Android_ResumeSem)) SDL_SemPost(Android_ResumeSem);
   702 }
   703 
   704 /* Pause */
   705 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativePause)(
   706                                     JNIEnv* env, jclass cls)
   707 {
   708     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativePause()");
   709 
   710     if (Android_Window) {
   711         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_LOST, 0, 0);
   712         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_MINIMIZED, 0, 0);
   713         SDL_SendAppEvent(SDL_APP_WILLENTERBACKGROUND);
   714         SDL_SendAppEvent(SDL_APP_DIDENTERBACKGROUND);
   715 
   716         /* *After* sending the relevant events, signal the pause semaphore
   717          * so the event loop knows to pause and (optionally) block itself */
   718         if (!SDL_SemValue(Android_PauseSem)) SDL_SemPost(Android_PauseSem);
   719     }
   720 }
   721 
   722 /* Resume */
   723 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeResume)(
   724                                     JNIEnv* env, jclass cls)
   725 {
   726     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeResume()");
   727 
   728     if (Android_Window) {
   729         SDL_SendAppEvent(SDL_APP_WILLENTERFOREGROUND);
   730         SDL_SendAppEvent(SDL_APP_DIDENTERFOREGROUND);
   731         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_GAINED, 0, 0);
   732         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_RESTORED, 0, 0);
   733         /* Signal the resume semaphore so the event loop knows to resume and restore the GL Context
   734          * We can't restore the GL Context here because it needs to be done on the SDL main thread
   735          * and this function will be called from the Java thread instead.
   736          */
   737         if (!SDL_SemValue(Android_ResumeSem)) SDL_SemPost(Android_ResumeSem);
   738     }
   739 }
   740 
   741 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeCommitText)(
   742                                     JNIEnv* env, jclass cls,
   743                                     jstring text, jint newCursorPosition)
   744 {
   745     const char *utftext = (*env)->GetStringUTFChars(env, text, NULL);
   746 
   747     SDL_SendKeyboardText(utftext);
   748 
   749     (*env)->ReleaseStringUTFChars(env, text, utftext);
   750 }
   751 
   752 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeSetComposingText)(
   753                                     JNIEnv* env, jclass cls,
   754                                     jstring text, jint newCursorPosition)
   755 {
   756     const char *utftext = (*env)->GetStringUTFChars(env, text, NULL);
   757 
   758     SDL_SendEditingText(utftext, 0, 0);
   759 
   760     (*env)->ReleaseStringUTFChars(env, text, utftext);
   761 }
   762 
   763 JNIEXPORT jstring JNICALL SDL_JAVA_INTERFACE(nativeGetHint)(
   764                                     JNIEnv* env, jclass cls,
   765                                     jstring name)
   766 {
   767     const char *utfname = (*env)->GetStringUTFChars(env, name, NULL);
   768     const char *hint = SDL_GetHint(utfname);
   769 
   770     jstring result = (*env)->NewStringUTF(env, hint);
   771     (*env)->ReleaseStringUTFChars(env, name, utfname);
   772 
   773     return result;
   774 }
   775 
   776 /*******************************************************************************
   777              Functions called by SDL into Java
   778 *******************************************************************************/
   779 
   780 static int s_active = 0;
   781 struct LocalReferenceHolder
   782 {
   783     JNIEnv *m_env;
   784     const char *m_func;
   785 };
   786 
   787 static struct LocalReferenceHolder LocalReferenceHolder_Setup(const char *func)
   788 {
   789     struct LocalReferenceHolder refholder;
   790     refholder.m_env = NULL;
   791     refholder.m_func = func;
   792 #ifdef DEBUG_JNI
   793     SDL_Log("Entering function %s", func);
   794 #endif
   795     return refholder;
   796 }
   797 
   798 static SDL_bool LocalReferenceHolder_Init(struct LocalReferenceHolder *refholder, JNIEnv *env)
   799 {
   800     const int capacity = 16;
   801     if ((*env)->PushLocalFrame(env, capacity) < 0) {
   802         SDL_SetError("Failed to allocate enough JVM local references");
   803         return SDL_FALSE;
   804     }
   805     ++s_active;
   806     refholder->m_env = env;
   807     return SDL_TRUE;
   808 }
   809 
   810 static void LocalReferenceHolder_Cleanup(struct LocalReferenceHolder *refholder)
   811 {
   812 #ifdef DEBUG_JNI
   813     SDL_Log("Leaving function %s", refholder->m_func);
   814 #endif
   815     if (refholder->m_env) {
   816         JNIEnv* env = refholder->m_env;
   817         (*env)->PopLocalFrame(env, NULL);
   818         --s_active;
   819     }
   820 }
   821 
   822 static SDL_bool LocalReferenceHolder_IsActive(void)
   823 {
   824     return s_active > 0;
   825 }
   826 
   827 ANativeWindow* Android_JNI_GetNativeWindow(void)
   828 {
   829     ANativeWindow* anw;
   830     jobject s;
   831     JNIEnv *env = Android_JNI_GetEnv();
   832 
   833     s = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetNativeSurface);
   834     anw = ANativeWindow_fromSurface(env, s);
   835     (*env)->DeleteLocalRef(env, s);
   836 
   837     return anw;
   838 }
   839 
   840 void Android_JNI_SetActivityTitle(const char *title)
   841 {
   842     JNIEnv *mEnv = Android_JNI_GetEnv();
   843 
   844     jstring jtitle = (jstring)((*mEnv)->NewStringUTF(mEnv, title));
   845     (*mEnv)->CallStaticBooleanMethod(mEnv, mActivityClass, midSetActivityTitle, jtitle);
   846     (*mEnv)->DeleteLocalRef(mEnv, jtitle);
   847 }
   848 
   849 void Android_JNI_SetOrientation(int w, int h, int resizable, const char *hint)
   850 {
   851     JNIEnv *mEnv = Android_JNI_GetEnv();
   852 
   853     jstring jhint = (jstring)((*mEnv)->NewStringUTF(mEnv, (hint ? hint : "")));
   854     (*mEnv)->CallStaticVoidMethod(mEnv, mActivityClass, midSetOrientation, w, h, (resizable? 1 : 0), jhint);
   855     (*mEnv)->DeleteLocalRef(mEnv, jhint);
   856 }
   857 
   858 SDL_bool Android_JNI_GetAccelerometerValues(float values[3])
   859 {
   860     int i;
   861     SDL_bool retval = SDL_FALSE;
   862 
   863     if (bHasNewData) {
   864         for (i = 0; i < 3; ++i) {
   865             values[i] = fLastAccelerometer[i];
   866         }
   867         bHasNewData = SDL_FALSE;
   868         retval = SDL_TRUE;
   869     }
   870 
   871     return retval;
   872 }
   873 
   874 static void Android_JNI_ThreadDestroyed(void* value)
   875 {
   876     /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */
   877     JNIEnv *env = (JNIEnv*) value;
   878     if (env != NULL) {
   879         (*mJavaVM)->DetachCurrentThread(mJavaVM);
   880         pthread_setspecific(mThreadKey, NULL);
   881     }
   882 }
   883 
   884 JNIEnv* Android_JNI_GetEnv(void)
   885 {
   886     /* From http://developer.android.com/guide/practices/jni.html
   887      * All threads are Linux threads, scheduled by the kernel.
   888      * They're usually started from managed code (using Thread.start), but they can also be created elsewhere and then
   889      * attached to the JavaVM. For example, a thread started with pthread_create can be attached with the
   890      * JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a thread is attached, it has no JNIEnv,
   891      * and cannot make JNI calls.
   892      * Attaching a natively-created thread causes a java.lang.Thread object to be constructed and added to the "main"
   893      * ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread on an already-attached thread
   894      * is a no-op.
   895      * Note: You can call this function any number of times for the same thread, there's no harm in it
   896      */
   897 
   898     JNIEnv *env;
   899     int status = (*mJavaVM)->AttachCurrentThread(mJavaVM, &env, NULL);
   900     if(status < 0) {
   901         LOGE("failed to attach current thread");
   902         return 0;
   903     }
   904 
   905     /* From http://developer.android.com/guide/practices/jni.html
   906      * Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward,
   907      * in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be
   908      * called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific
   909      * to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.)
   910      * Note: The destructor is not called unless the stored value is != NULL
   911      * Note: You can call this function any number of times for the same thread, there's no harm in it
   912      *       (except for some lost CPU cycles)
   913      */
   914     pthread_setspecific(mThreadKey, (void*) env);
   915 
   916     return env;
   917 }
   918 
   919 int Android_JNI_SetupThread(void)
   920 {
   921     Android_JNI_GetEnv();
   922     return 1;
   923 }
   924 
   925 /*
   926  * Audio support
   927  */
   928 static jboolean audioBuffer16Bit = JNI_FALSE;
   929 static jobject audioBuffer = NULL;
   930 static void* audioBufferPinned = NULL;
   931 static jboolean captureBuffer16Bit = JNI_FALSE;
   932 static jobject captureBuffer = NULL;
   933 
   934 int Android_JNI_OpenAudioDevice(int iscapture, int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
   935 {
   936     jboolean audioBufferStereo;
   937     int audioBufferFrames;
   938     jobject jbufobj = NULL;
   939     jboolean isCopy;
   940 
   941     JNIEnv *env = Android_JNI_GetEnv();
   942 
   943     if (!env) {
   944         LOGE("callback_handler: failed to attach current thread");
   945     }
   946     Android_JNI_SetupThread();
   947 
   948     audioBufferStereo = channelCount > 1;
   949 
   950     if (iscapture) {
   951         __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device for capture");
   952         captureBuffer16Bit = is16Bit;
   953         if ((*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureOpen, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames) != 0) {
   954             /* Error during audio initialization */
   955             __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: error on AudioRecord initialization!");
   956             return 0;
   957         }
   958     } else {
   959         __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device for output");
   960         audioBuffer16Bit = is16Bit;
   961         if ((*env)->CallStaticIntMethod(env, mAudioManagerClass, midAudioOpen, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames) != 0) {
   962             /* Error during audio initialization */
   963             __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: error on AudioTrack initialization!");
   964             return 0;
   965         }
   966     }
   967 
   968     /* Allocating the audio buffer from the Java side and passing it as the return value for audioInit no longer works on
   969      * Android >= 4.2 due to a "stale global reference" error. So now we allocate this buffer directly from this side. */
   970 
   971     if (is16Bit) {
   972         jshortArray audioBufferLocal = (*env)->NewShortArray(env, desiredBufferFrames * (audioBufferStereo ? 2 : 1));
   973         if (audioBufferLocal) {
   974             jbufobj = (*env)->NewGlobalRef(env, audioBufferLocal);
   975             (*env)->DeleteLocalRef(env, audioBufferLocal);
   976         }
   977     }
   978     else {
   979         jbyteArray audioBufferLocal = (*env)->NewByteArray(env, desiredBufferFrames * (audioBufferStereo ? 2 : 1));
   980         if (audioBufferLocal) {
   981             jbufobj = (*env)->NewGlobalRef(env, audioBufferLocal);
   982             (*env)->DeleteLocalRef(env, audioBufferLocal);
   983         }
   984     }
   985 
   986     if (jbufobj == NULL) {
   987         __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: could not allocate an audio buffer!");
   988         return 0;
   989     }
   990 
   991     if (iscapture) {
   992         captureBuffer = jbufobj;
   993     } else {
   994         audioBuffer = jbufobj;
   995     }
   996 
   997     isCopy = JNI_FALSE;
   998 
   999     if (is16Bit) {
  1000         if (!iscapture) {
  1001             audioBufferPinned = (*env)->GetShortArrayElements(env, (jshortArray)audioBuffer, &isCopy);
  1002         }
  1003         audioBufferFrames = (*env)->GetArrayLength(env, (jshortArray)audioBuffer);
  1004     } else {
  1005         if (!iscapture) {
  1006             audioBufferPinned = (*env)->GetByteArrayElements(env, (jbyteArray)audioBuffer, &isCopy);
  1007         }
  1008         audioBufferFrames = (*env)->GetArrayLength(env, (jbyteArray)audioBuffer);
  1009     }
  1010 
  1011     if (audioBufferStereo) {
  1012         audioBufferFrames /= 2;
  1013     }
  1014 
  1015     return audioBufferFrames;
  1016 }
  1017 
  1018 void * Android_JNI_GetAudioBuffer(void)
  1019 {
  1020     return audioBufferPinned;
  1021 }
  1022 
  1023 void Android_JNI_WriteAudioBuffer(void)
  1024 {
  1025     JNIEnv *mAudioEnv = Android_JNI_GetEnv();
  1026 
  1027     if (audioBuffer16Bit) {
  1028         (*mAudioEnv)->ReleaseShortArrayElements(mAudioEnv, (jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
  1029         (*mAudioEnv)->CallStaticVoidMethod(mAudioEnv, mAudioManagerClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
  1030     } else {
  1031         (*mAudioEnv)->ReleaseByteArrayElements(mAudioEnv, (jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT);
  1032         (*mAudioEnv)->CallStaticVoidMethod(mAudioEnv, mAudioManagerClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer);
  1033     }
  1034 
  1035     /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
  1036 }
  1037 
  1038 int Android_JNI_CaptureAudioBuffer(void *buffer, int buflen)
  1039 {
  1040     JNIEnv *env = Android_JNI_GetEnv();
  1041     jboolean isCopy = JNI_FALSE;
  1042     jint br;
  1043 
  1044     if (captureBuffer16Bit) {
  1045         SDL_assert((*env)->GetArrayLength(env, (jshortArray)captureBuffer) == (buflen / 2));
  1046         br = (*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureReadShortBuffer, (jshortArray)captureBuffer, JNI_TRUE);
  1047         if (br > 0) {
  1048             jshort *ptr = (*env)->GetShortArrayElements(env, (jshortArray)captureBuffer, &isCopy);
  1049             br *= 2;
  1050             SDL_memcpy(buffer, ptr, br);
  1051             (*env)->ReleaseShortArrayElements(env, (jshortArray)captureBuffer, (jshort *)ptr, JNI_ABORT);
  1052         }
  1053     } else {
  1054         SDL_assert((*env)->GetArrayLength(env, (jshortArray)captureBuffer) == buflen);
  1055         br = (*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureReadByteBuffer, (jbyteArray)captureBuffer, JNI_TRUE);
  1056         if (br > 0) {
  1057             jbyte *ptr = (*env)->GetByteArrayElements(env, (jbyteArray)captureBuffer, &isCopy);
  1058             SDL_memcpy(buffer, ptr, br);
  1059             (*env)->ReleaseByteArrayElements(env, (jbyteArray)captureBuffer, (jbyte *)ptr, JNI_ABORT);
  1060         }
  1061     }
  1062 
  1063     return (int) br;
  1064 }
  1065 
  1066 void Android_JNI_FlushCapturedAudio(void)
  1067 {
  1068     JNIEnv *env = Android_JNI_GetEnv();
  1069 #if 0  /* !!! FIXME: this needs API 23, or it'll do blocking reads and never end. */
  1070     if (captureBuffer16Bit) {
  1071         const jint len = (*env)->GetArrayLength(env, (jshortArray)captureBuffer);
  1072         while ((*env)->CallStaticIntMethod(env, mActivityClass, midCaptureReadShortBuffer, (jshortArray)captureBuffer, JNI_FALSE) == len) { /* spin */ }
  1073     } else {
  1074         const jint len = (*env)->GetArrayLength(env, (jbyteArray)captureBuffer);
  1075         while ((*env)->CallStaticIntMethod(env, mActivityClass, midCaptureReadByteBuffer, (jbyteArray)captureBuffer, JNI_FALSE) == len) { /* spin */ }
  1076     }
  1077 #else
  1078     if (captureBuffer16Bit) {
  1079         (*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureReadShortBuffer, (jshortArray)captureBuffer, JNI_FALSE);
  1080     } else {
  1081         (*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureReadByteBuffer, (jbyteArray)captureBuffer, JNI_FALSE);
  1082     }
  1083 #endif
  1084 }
  1085 
  1086 void Android_JNI_CloseAudioDevice(const int iscapture)
  1087 {
  1088     JNIEnv *env = Android_JNI_GetEnv();
  1089 
  1090     if (iscapture) {
  1091         (*env)->CallStaticVoidMethod(env, mAudioManagerClass, midCaptureClose);
  1092         if (captureBuffer) {
  1093             (*env)->DeleteGlobalRef(env, captureBuffer);
  1094             captureBuffer = NULL;
  1095         }
  1096     } else {
  1097         (*env)->CallStaticVoidMethod(env, mAudioManagerClass, midAudioClose);
  1098         if (audioBuffer) {
  1099             (*env)->DeleteGlobalRef(env, audioBuffer);
  1100             audioBuffer = NULL;
  1101             audioBufferPinned = NULL;
  1102         }
  1103     }
  1104 }
  1105 
  1106 /* Test for an exception and call SDL_SetError with its detail if one occurs */
  1107 /* If the parameter silent is truthy then SDL_SetError() will not be called. */
  1108 static SDL_bool Android_JNI_ExceptionOccurred(SDL_bool silent)
  1109 {
  1110     JNIEnv *mEnv = Android_JNI_GetEnv();
  1111     jthrowable exception;
  1112 
  1113     SDL_assert(LocalReferenceHolder_IsActive());
  1114 
  1115     exception = (*mEnv)->ExceptionOccurred(mEnv);
  1116     if (exception != NULL) {
  1117         jmethodID mid;
  1118 
  1119         /* Until this happens most JNI operations have undefined behaviour */
  1120         (*mEnv)->ExceptionClear(mEnv);
  1121 
  1122         if (!silent) {
  1123             jclass exceptionClass = (*mEnv)->GetObjectClass(mEnv, exception);
  1124             jclass classClass = (*mEnv)->FindClass(mEnv, "java/lang/Class");
  1125             jstring exceptionName;
  1126             const char* exceptionNameUTF8;
  1127             jstring exceptionMessage;
  1128 
  1129             mid = (*mEnv)->GetMethodID(mEnv, classClass, "getName", "()Ljava/lang/String;");
  1130             exceptionName = (jstring)(*mEnv)->CallObjectMethod(mEnv, exceptionClass, mid);
  1131             exceptionNameUTF8 = (*mEnv)->GetStringUTFChars(mEnv, exceptionName, 0);
  1132 
  1133             mid = (*mEnv)->GetMethodID(mEnv, exceptionClass, "getMessage", "()Ljava/lang/String;");
  1134             exceptionMessage = (jstring)(*mEnv)->CallObjectMethod(mEnv, exception, mid);
  1135 
  1136             if (exceptionMessage != NULL) {
  1137                 const char* exceptionMessageUTF8 = (*mEnv)->GetStringUTFChars(mEnv, exceptionMessage, 0);
  1138                 SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
  1139                 (*mEnv)->ReleaseStringUTFChars(mEnv, exceptionMessage, exceptionMessageUTF8);
  1140             } else {
  1141                 SDL_SetError("%s", exceptionNameUTF8);
  1142             }
  1143 
  1144             (*mEnv)->ReleaseStringUTFChars(mEnv, exceptionName, exceptionNameUTF8);
  1145         }
  1146 
  1147         return SDL_TRUE;
  1148     }
  1149 
  1150     return SDL_FALSE;
  1151 }
  1152 
  1153 static int Internal_Android_JNI_FileOpen(SDL_RWops* ctx)
  1154 {
  1155     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  1156 
  1157     int result = 0;
  1158 
  1159     jmethodID mid;
  1160     jobject context;
  1161     jobject assetManager;
  1162     jobject inputStream;
  1163     jclass channels;
  1164     jobject readableByteChannel;
  1165     jstring fileNameJString;
  1166     jobject fd;
  1167     jclass fdCls;
  1168     jfieldID descriptor;
  1169 
  1170     JNIEnv *mEnv = Android_JNI_GetEnv();
  1171     if (!LocalReferenceHolder_Init(&refs, mEnv)) {
  1172         goto failure;
  1173     }
  1174 
  1175     fileNameJString = (jstring)ctx->hidden.androidio.fileNameRef;
  1176     ctx->hidden.androidio.position = 0;
  1177 
  1178     /* context = SDLActivity.getContext(); */
  1179     context = (*mEnv)->CallStaticObjectMethod(mEnv, mActivityClass, midGetContext);
  1180 
  1181     /* assetManager = context.getAssets(); */
  1182     mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, context),
  1183             "getAssets", "()Landroid/content/res/AssetManager;");
  1184     assetManager = (*mEnv)->CallObjectMethod(mEnv, context, mid);
  1185 
  1186     /* First let's try opening the file to obtain an AssetFileDescriptor.
  1187     * This method reads the files directly from the APKs using standard *nix calls
  1188     */
  1189     mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, assetManager), "openFd", "(Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;");
  1190     inputStream = (*mEnv)->CallObjectMethod(mEnv, assetManager, mid, fileNameJString);
  1191     if (Android_JNI_ExceptionOccurred(SDL_TRUE)) {
  1192         goto fallback;
  1193     }
  1194 
  1195     mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getStartOffset", "()J");
  1196     ctx->hidden.androidio.offset = (*mEnv)->CallLongMethod(mEnv, inputStream, mid);
  1197     if (Android_JNI_ExceptionOccurred(SDL_TRUE)) {
  1198         goto fallback;
  1199     }
  1200 
  1201     mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getDeclaredLength", "()J");
  1202     ctx->hidden.androidio.size = (*mEnv)->CallLongMethod(mEnv, inputStream, mid);
  1203     if (Android_JNI_ExceptionOccurred(SDL_TRUE)) {
  1204         goto fallback;
  1205     }
  1206 
  1207     mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getFileDescriptor", "()Ljava/io/FileDescriptor;");
  1208     fd = (*mEnv)->CallObjectMethod(mEnv, inputStream, mid);
  1209     fdCls = (*mEnv)->GetObjectClass(mEnv, fd);
  1210     descriptor = (*mEnv)->GetFieldID(mEnv, fdCls, "descriptor", "I");
  1211     ctx->hidden.androidio.fd = (*mEnv)->GetIntField(mEnv, fd, descriptor);
  1212     ctx->hidden.androidio.assetFileDescriptorRef = (*mEnv)->NewGlobalRef(mEnv, inputStream);
  1213 
  1214     /* Seek to the correct offset in the file. */
  1215     lseek(ctx->hidden.androidio.fd, (off_t)ctx->hidden.androidio.offset, SEEK_SET);
  1216 
  1217     if (0) {
  1218 fallback:
  1219         /* Disabled log message because of spam on the Nexus 7 */
  1220         /* __android_log_print(ANDROID_LOG_DEBUG, "SDL", "Falling back to legacy InputStream method for opening file"); */
  1221 
  1222         /* Try the old method using InputStream */
  1223         ctx->hidden.androidio.assetFileDescriptorRef = NULL;
  1224 
  1225         /* inputStream = assetManager.open(<filename>); */
  1226         mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, assetManager),
  1227                 "open", "(Ljava/lang/String;I)Ljava/io/InputStream;");
  1228         inputStream = (*mEnv)->CallObjectMethod(mEnv, assetManager, mid, fileNameJString, 1 /* ACCESS_RANDOM */);
  1229         if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
  1230             /* Try fallback to APK expansion files */
  1231             inputStream = (*mEnv)->CallStaticObjectMethod(mEnv, mActivityClass, midOpenAPKExpansionInputStream, fileNameJString);
  1232 
  1233             /* Exception is checked first because it always needs to be cleared.
  1234              * If no exception occurred then the last SDL error message is kept.
  1235              */
  1236             if (Android_JNI_ExceptionOccurred(SDL_FALSE) || !inputStream) {
  1237                 goto failure;
  1238             }
  1239         }
  1240 
  1241         ctx->hidden.androidio.inputStreamRef = (*mEnv)->NewGlobalRef(mEnv, inputStream);
  1242 
  1243         /* Despite all the visible documentation on [Asset]InputStream claiming
  1244          * that the .available() method is not guaranteed to return the entire file
  1245          * size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
  1246          * android/apis/content/ReadAsset.java imply that Android's
  1247          * AssetInputStream.available() /will/ always return the total file size
  1248         */
  1249 
  1250         /* size = inputStream.available(); */
  1251         mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream),
  1252                 "available", "()I");
  1253         ctx->hidden.androidio.size = (long)(*mEnv)->CallIntMethod(mEnv, inputStream, mid);
  1254         if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
  1255             goto failure;
  1256         }
  1257 
  1258         /* readableByteChannel = Channels.newChannel(inputStream); */
  1259         channels = (*mEnv)->FindClass(mEnv, "java/nio/channels/Channels");
  1260         mid = (*mEnv)->GetStaticMethodID(mEnv, channels,
  1261                 "newChannel",
  1262                 "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
  1263         readableByteChannel = (*mEnv)->CallStaticObjectMethod(
  1264                 mEnv, channels, mid, inputStream);
  1265         if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
  1266             goto failure;
  1267         }
  1268 
  1269         ctx->hidden.androidio.readableByteChannelRef =
  1270             (*mEnv)->NewGlobalRef(mEnv, readableByteChannel);
  1271 
  1272         /* Store .read id for reading purposes */
  1273         mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, readableByteChannel),
  1274                 "read", "(Ljava/nio/ByteBuffer;)I");
  1275         ctx->hidden.androidio.readMethod = mid;
  1276     }
  1277 
  1278     if (0) {
  1279 failure:
  1280         result = -1;
  1281 
  1282         (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.fileNameRef);
  1283 
  1284         if(ctx->hidden.androidio.inputStreamRef != NULL) {
  1285             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.inputStreamRef);
  1286         }
  1287 
  1288         if(ctx->hidden.androidio.readableByteChannelRef != NULL) {
  1289             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.readableByteChannelRef);
  1290         }
  1291 
  1292         if(ctx->hidden.androidio.assetFileDescriptorRef != NULL) {
  1293             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.assetFileDescriptorRef);
  1294         }
  1295 
  1296     }
  1297 
  1298     LocalReferenceHolder_Cleanup(&refs);
  1299     return result;
  1300 }
  1301 
  1302 int Android_JNI_FileOpen(SDL_RWops* ctx,
  1303         const char* fileName, const char* mode)
  1304 {
  1305     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  1306     JNIEnv *mEnv = Android_JNI_GetEnv();
  1307     int retval;
  1308     jstring fileNameJString;
  1309 
  1310     if (!LocalReferenceHolder_Init(&refs, mEnv)) {
  1311         LocalReferenceHolder_Cleanup(&refs);
  1312         return -1;
  1313     }
  1314 
  1315     if (!ctx) {
  1316         LocalReferenceHolder_Cleanup(&refs);
  1317         return -1;
  1318     }
  1319 
  1320     fileNameJString = (*mEnv)->NewStringUTF(mEnv, fileName);
  1321     ctx->hidden.androidio.fileNameRef = (*mEnv)->NewGlobalRef(mEnv, fileNameJString);
  1322     ctx->hidden.androidio.inputStreamRef = NULL;
  1323     ctx->hidden.androidio.readableByteChannelRef = NULL;
  1324     ctx->hidden.androidio.readMethod = NULL;
  1325     ctx->hidden.androidio.assetFileDescriptorRef = NULL;
  1326 
  1327     retval = Internal_Android_JNI_FileOpen(ctx);
  1328     LocalReferenceHolder_Cleanup(&refs);
  1329     return retval;
  1330 }
  1331 
  1332 size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
  1333         size_t size, size_t maxnum)
  1334 {
  1335     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  1336 
  1337     if (ctx->hidden.androidio.assetFileDescriptorRef) {
  1338         size_t bytesMax = size * maxnum;
  1339         size_t result;
  1340         if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && ctx->hidden.androidio.position + bytesMax > ctx->hidden.androidio.size) {
  1341             bytesMax = ctx->hidden.androidio.size - ctx->hidden.androidio.position;
  1342         }
  1343         result = read(ctx->hidden.androidio.fd, buffer, bytesMax );
  1344         if (result > 0) {
  1345             ctx->hidden.androidio.position += result;
  1346             LocalReferenceHolder_Cleanup(&refs);
  1347             return result / size;
  1348         }
  1349         LocalReferenceHolder_Cleanup(&refs);
  1350         return 0;
  1351     } else {
  1352         jlong bytesRemaining = (jlong) (size * maxnum);
  1353         jlong bytesMax = (jlong) (ctx->hidden.androidio.size -  ctx->hidden.androidio.position);
  1354         int bytesRead = 0;
  1355         JNIEnv *mEnv;
  1356         jobject readableByteChannel;
  1357         jmethodID readMethod;
  1358         jobject byteBuffer;
  1359 
  1360         /* Don't read more bytes than those that remain in the file, otherwise we get an exception */
  1361         if (bytesRemaining >  bytesMax) bytesRemaining = bytesMax;
  1362 
  1363         mEnv = Android_JNI_GetEnv();
  1364         if (!LocalReferenceHolder_Init(&refs, mEnv)) {
  1365             LocalReferenceHolder_Cleanup(&refs);
  1366             return 0;
  1367         }
  1368 
  1369         readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannelRef;
  1370         readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
  1371         byteBuffer = (*mEnv)->NewDirectByteBuffer(mEnv, buffer, bytesRemaining);
  1372 
  1373         while (bytesRemaining > 0) {
  1374             /* result = readableByteChannel.read(...); */
  1375             int result = (*mEnv)->CallIntMethod(mEnv, readableByteChannel, readMethod, byteBuffer);
  1376 
  1377             if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
  1378                 LocalReferenceHolder_Cleanup(&refs);
  1379                 return 0;
  1380             }
  1381 
  1382             if (result < 0) {
  1383                 break;
  1384             }
  1385 
  1386             bytesRemaining -= result;
  1387             bytesRead += result;
  1388             ctx->hidden.androidio.position += result;
  1389         }
  1390         LocalReferenceHolder_Cleanup(&refs);
  1391         return bytesRead / size;
  1392     }
  1393 }
  1394 
  1395 size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
  1396         size_t size, size_t num)
  1397 {
  1398     SDL_SetError("Cannot write to Android package filesystem");
  1399     return 0;
  1400 }
  1401 
  1402 static int Internal_Android_JNI_FileClose(SDL_RWops* ctx, SDL_bool release)
  1403 {
  1404     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  1405 
  1406     int result = 0;
  1407     JNIEnv *mEnv = Android_JNI_GetEnv();
  1408 
  1409     if (!LocalReferenceHolder_Init(&refs, mEnv)) {
  1410         LocalReferenceHolder_Cleanup(&refs);
  1411         return SDL_SetError("Failed to allocate enough JVM local references");
  1412     }
  1413 
  1414     if (ctx) {
  1415         if (release) {
  1416             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.fileNameRef);
  1417         }
  1418 
  1419         if (ctx->hidden.androidio.assetFileDescriptorRef) {
  1420             jobject inputStream = (jobject)ctx->hidden.androidio.assetFileDescriptorRef;
  1421             jmethodID mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream),
  1422                     "close", "()V");
  1423             (*mEnv)->CallVoidMethod(mEnv, inputStream, mid);
  1424             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.assetFileDescriptorRef);
  1425             if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
  1426                 result = -1;
  1427             }
  1428         }
  1429         else {
  1430             jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
  1431 
  1432             /* inputStream.close(); */
  1433             jmethodID mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream),
  1434                     "close", "()V");
  1435             (*mEnv)->CallVoidMethod(mEnv, inputStream, mid);
  1436             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.inputStreamRef);
  1437             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.readableByteChannelRef);
  1438             if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
  1439                 result = -1;
  1440             }
  1441         }
  1442 
  1443         if (release) {
  1444             SDL_FreeRW(ctx);
  1445         }
  1446     }
  1447 
  1448     LocalReferenceHolder_Cleanup(&refs);
  1449     return result;
  1450 }
  1451 
  1452 
  1453 Sint64 Android_JNI_FileSize(SDL_RWops* ctx)
  1454 {
  1455     return ctx->hidden.androidio.size;
  1456 }
  1457 
  1458 Sint64 Android_JNI_FileSeek(SDL_RWops* ctx, Sint64 offset, int whence)
  1459 {
  1460     if (ctx->hidden.androidio.assetFileDescriptorRef) {
  1461         off_t ret;
  1462         switch (whence) {
  1463             case RW_SEEK_SET:
  1464                 if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
  1465                 offset += ctx->hidden.androidio.offset;
  1466                 break;
  1467             case RW_SEEK_CUR:
  1468                 offset += ctx->hidden.androidio.position;
  1469                 if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
  1470                 offset += ctx->hidden.androidio.offset;
  1471                 break;
  1472             case RW_SEEK_END:
  1473                 offset = ctx->hidden.androidio.offset + ctx->hidden.androidio.size + offset;
  1474                 break;
  1475             default:
  1476                 return SDL_SetError("Unknown value for 'whence'");
  1477         }
  1478         whence = SEEK_SET;
  1479 
  1480         ret = lseek(ctx->hidden.androidio.fd, (off_t)offset, SEEK_SET);
  1481         if (ret == -1) return -1;
  1482         ctx->hidden.androidio.position = ret - ctx->hidden.androidio.offset;
  1483     } else {
  1484         Sint64 newPosition;
  1485         Sint64 movement;
  1486 
  1487         switch (whence) {
  1488             case RW_SEEK_SET:
  1489                 newPosition = offset;
  1490                 break;
  1491             case RW_SEEK_CUR:
  1492                 newPosition = ctx->hidden.androidio.position + offset;
  1493                 break;
  1494             case RW_SEEK_END:
  1495                 newPosition = ctx->hidden.androidio.size + offset;
  1496                 break;
  1497             default:
  1498                 return SDL_SetError("Unknown value for 'whence'");
  1499         }
  1500 
  1501         /* Validate the new position */
  1502         if (newPosition < 0) {
  1503             return SDL_Error(SDL_EFSEEK);
  1504         }
  1505         if (newPosition > ctx->hidden.androidio.size) {
  1506             newPosition = ctx->hidden.androidio.size;
  1507         }
  1508 
  1509         movement = newPosition - ctx->hidden.androidio.position;
  1510         if (movement > 0) {
  1511             unsigned char buffer[4096];
  1512 
  1513             /* The easy case where we're seeking forwards */
  1514             while (movement > 0) {
  1515                 Sint64 amount = sizeof (buffer);
  1516                 size_t result;
  1517                 if (amount > movement) {
  1518                     amount = movement;
  1519                 }
  1520                 result = Android_JNI_FileRead(ctx, buffer, 1, amount);
  1521                 if (result <= 0) {
  1522                     /* Failed to read/skip the required amount, so fail */
  1523                     return -1;
  1524                 }
  1525 
  1526                 movement -= result;
  1527             }
  1528 
  1529         } else if (movement < 0) {
  1530             /* We can't seek backwards so we have to reopen the file and seek */
  1531             /* forwards which obviously isn't very efficient */
  1532             Internal_Android_JNI_FileClose(ctx, SDL_FALSE);
  1533             Internal_Android_JNI_FileOpen(ctx);
  1534             Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
  1535         }
  1536     }
  1537 
  1538     return ctx->hidden.androidio.position;
  1539 
  1540 }
  1541 
  1542 int Android_JNI_FileClose(SDL_RWops* ctx)
  1543 {
  1544     return Internal_Android_JNI_FileClose(ctx, SDL_TRUE);
  1545 }
  1546 
  1547 int Android_JNI_SetClipboardText(const char* text)
  1548 {
  1549     JNIEnv* env = Android_JNI_GetEnv();
  1550     jstring string = (*env)->NewStringUTF(env, text);
  1551     (*env)->CallStaticVoidMethod(env, mActivityClass, midClipboardSetText, string);
  1552     (*env)->DeleteLocalRef(env, string);
  1553     return 0;
  1554 }
  1555 
  1556 char* Android_JNI_GetClipboardText(void)
  1557 {
  1558     JNIEnv* env = Android_JNI_GetEnv();
  1559     char* text = NULL;
  1560     jstring string;
  1561     
  1562     string = (*env)->CallStaticObjectMethod(env, mActivityClass, midClipboardGetText);
  1563     if (string) {
  1564         const char* utf = (*env)->GetStringUTFChars(env, string, 0);
  1565         if (utf) {
  1566             text = SDL_strdup(utf);
  1567             (*env)->ReleaseStringUTFChars(env, string, utf);
  1568         }
  1569         (*env)->DeleteLocalRef(env, string);
  1570     }
  1571     
  1572     return (text == NULL) ? SDL_strdup("") : text;
  1573 }
  1574 
  1575 SDL_bool Android_JNI_HasClipboardText(void)
  1576 {
  1577     JNIEnv* env = Android_JNI_GetEnv();
  1578     jboolean retval = (*env)->CallStaticBooleanMethod(env, mActivityClass, midClipboardHasText);
  1579     return (retval == JNI_TRUE) ? SDL_TRUE : SDL_FALSE;
  1580 }
  1581 
  1582 /* returns 0 on success or -1 on error (others undefined then)
  1583  * returns truthy or falsy value in plugged, charged and battery
  1584  * returns the value in seconds and percent or -1 if not available
  1585  */
  1586 int Android_JNI_GetPowerInfo(int* plugged, int* charged, int* battery, int* seconds, int* percent)
  1587 {
  1588     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  1589     JNIEnv* env = Android_JNI_GetEnv();
  1590     jmethodID mid;
  1591     jobject context;
  1592     jstring action;
  1593     jclass cls;
  1594     jobject filter;
  1595     jobject intent;
  1596     jstring iname;
  1597     jmethodID imid;
  1598     jstring bname;
  1599     jmethodID bmid;
  1600     if (!LocalReferenceHolder_Init(&refs, env)) {
  1601         LocalReferenceHolder_Cleanup(&refs);
  1602         return -1;
  1603     }
  1604 
  1605 
  1606     /* context = SDLActivity.getContext(); */
  1607     context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
  1608 
  1609     action = (*env)->NewStringUTF(env, "android.intent.action.BATTERY_CHANGED");
  1610 
  1611     cls = (*env)->FindClass(env, "android/content/IntentFilter");
  1612 
  1613     mid = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/lang/String;)V");
  1614     filter = (*env)->NewObject(env, cls, mid, action);
  1615 
  1616     (*env)->DeleteLocalRef(env, action);
  1617 
  1618     mid = (*env)->GetMethodID(env, mActivityClass, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;");
  1619     intent = (*env)->CallObjectMethod(env, context, mid, NULL, filter);
  1620 
  1621     (*env)->DeleteLocalRef(env, filter);
  1622 
  1623     cls = (*env)->GetObjectClass(env, intent);
  1624 
  1625     imid = (*env)->GetMethodID(env, cls, "getIntExtra", "(Ljava/lang/String;I)I");
  1626 
  1627     /* Watch out for C89 scoping rules because of the macro */
  1628 #define GET_INT_EXTRA(var, key) \
  1629     int var; \
  1630     iname = (*env)->NewStringUTF(env, key); \
  1631     var = (*env)->CallIntMethod(env, intent, imid, iname, -1); \
  1632     (*env)->DeleteLocalRef(env, iname);
  1633 
  1634     bmid = (*env)->GetMethodID(env, cls, "getBooleanExtra", "(Ljava/lang/String;Z)Z");
  1635 
  1636     /* Watch out for C89 scoping rules because of the macro */
  1637 #define GET_BOOL_EXTRA(var, key) \
  1638     int var; \
  1639     bname = (*env)->NewStringUTF(env, key); \
  1640     var = (*env)->CallBooleanMethod(env, intent, bmid, bname, JNI_FALSE); \
  1641     (*env)->DeleteLocalRef(env, bname);
  1642 
  1643     if (plugged) {
  1644         /* Watch out for C89 scoping rules because of the macro */
  1645         GET_INT_EXTRA(plug, "plugged") /* == BatteryManager.EXTRA_PLUGGED (API 5) */
  1646         if (plug == -1) {
  1647             LocalReferenceHolder_Cleanup(&refs);
  1648             return -1;
  1649         }
  1650         /* 1 == BatteryManager.BATTERY_PLUGGED_AC */
  1651         /* 2 == BatteryManager.BATTERY_PLUGGED_USB */
  1652         *plugged = (0 < plug) ? 1 : 0;
  1653     }
  1654 
  1655     if (charged) {
  1656         /* Watch out for C89 scoping rules because of the macro */
  1657         GET_INT_EXTRA(status, "status") /* == BatteryManager.EXTRA_STATUS (API 5) */
  1658         if (status == -1) {
  1659             LocalReferenceHolder_Cleanup(&refs);
  1660             return -1;
  1661         }
  1662         /* 5 == BatteryManager.BATTERY_STATUS_FULL */
  1663         *charged = (status == 5) ? 1 : 0;
  1664     }
  1665 
  1666     if (battery) {
  1667         GET_BOOL_EXTRA(present, "present") /* == BatteryManager.EXTRA_PRESENT (API 5) */
  1668         *battery = present ? 1 : 0;
  1669     }
  1670 
  1671     if (seconds) {
  1672         *seconds = -1; /* not possible */
  1673     }
  1674 
  1675     if (percent) {
  1676         int level;
  1677         int scale;
  1678 
  1679         /* Watch out for C89 scoping rules because of the macro */
  1680         {
  1681             GET_INT_EXTRA(level_temp, "level") /* == BatteryManager.EXTRA_LEVEL (API 5) */
  1682             level = level_temp;
  1683         }
  1684         /* Watch out for C89 scoping rules because of the macro */
  1685         {
  1686             GET_INT_EXTRA(scale_temp, "scale") /* == BatteryManager.EXTRA_SCALE (API 5) */
  1687             scale = scale_temp;
  1688         }
  1689 
  1690         if ((level == -1) || (scale == -1)) {
  1691             LocalReferenceHolder_Cleanup(&refs);
  1692             return -1;
  1693         }
  1694         *percent = level * 100 / scale;
  1695     }
  1696 
  1697     (*env)->DeleteLocalRef(env, intent);
  1698 
  1699     LocalReferenceHolder_Cleanup(&refs);
  1700     return 0;
  1701 }
  1702 
  1703 /* returns number of found touch devices as return value and ids in parameter ids */
  1704 int Android_JNI_GetTouchDeviceIds(int **ids) {
  1705     JNIEnv *env = Android_JNI_GetEnv();
  1706     jint sources = 4098; /* == InputDevice.SOURCE_TOUCHSCREEN */
  1707     jintArray array = (jintArray) (*env)->CallStaticObjectMethod(env, mActivityClass, midInputGetInputDeviceIds, sources);
  1708     int number = 0;
  1709     *ids = NULL;
  1710     if (array) {
  1711         number = (int) (*env)->GetArrayLength(env, array);
  1712         if (0 < number) {
  1713             jint* elements = (*env)->GetIntArrayElements(env, array, NULL);
  1714             if (elements) {
  1715                 int i;
  1716                 *ids = SDL_malloc(number * sizeof (**ids));
  1717                 for (i = 0; i < number; ++i) { /* not assuming sizeof (jint) == sizeof (int) */
  1718                     (*ids)[i] = elements[i];
  1719                 }
  1720                 (*env)->ReleaseIntArrayElements(env, array, elements, JNI_ABORT);
  1721             }
  1722         }
  1723         (*env)->DeleteLocalRef(env, array);
  1724     }
  1725     return number;
  1726 }
  1727 
  1728 /* sets the mSeparateMouseAndTouch field */
  1729 void Android_JNI_SetSeparateMouseAndTouch(SDL_bool new_value)
  1730 {
  1731     JNIEnv *env = Android_JNI_GetEnv();
  1732     (*env)->SetStaticBooleanField(env, mActivityClass, fidSeparateMouseAndTouch, new_value ? JNI_TRUE : JNI_FALSE);
  1733 }
  1734 
  1735 void Android_JNI_PollInputDevices(void)
  1736 {
  1737     JNIEnv *env = Android_JNI_GetEnv();
  1738     (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midPollInputDevices);
  1739 }
  1740 
  1741 void Android_JNI_PollHapticDevices(void)
  1742 {
  1743     JNIEnv *env = Android_JNI_GetEnv();
  1744     (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midPollHapticDevices);
  1745 }
  1746 
  1747 void Android_JNI_HapticRun(int device_id, int length)
  1748 {
  1749     JNIEnv *env = Android_JNI_GetEnv();
  1750     (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midHapticRun, device_id, length);
  1751 }
  1752 
  1753 
  1754 /* See SDLActivity.java for constants. */
  1755 #define COMMAND_SET_KEEP_SCREEN_ON    5
  1756 
  1757 /* sends message to be handled on the UI event dispatch thread */
  1758 int Android_JNI_SendMessage(int command, int param)
  1759 {
  1760     JNIEnv *env = Android_JNI_GetEnv();
  1761     jboolean success;
  1762     success = (*env)->CallStaticBooleanMethod(env, mActivityClass, midSendMessage, command, param);
  1763     return success ? 0 : -1;
  1764 }
  1765 
  1766 void Android_JNI_SuspendScreenSaver(SDL_bool suspend)
  1767 {
  1768     Android_JNI_SendMessage(COMMAND_SET_KEEP_SCREEN_ON, (suspend == SDL_FALSE) ? 0 : 1);
  1769 }
  1770 
  1771 void Android_JNI_ShowTextInput(SDL_Rect *inputRect)
  1772 {
  1773     JNIEnv *env = Android_JNI_GetEnv();
  1774     (*env)->CallStaticBooleanMethod(env, mActivityClass, midShowTextInput,
  1775                                inputRect->x,
  1776                                inputRect->y,
  1777                                inputRect->w,
  1778                                inputRect->h );
  1779 }
  1780 
  1781 void Android_JNI_HideTextInput(void)
  1782 {
  1783     /* has to match Activity constant */
  1784     const int COMMAND_TEXTEDIT_HIDE = 3;
  1785     Android_JNI_SendMessage(COMMAND_TEXTEDIT_HIDE, 0);
  1786 }
  1787 
  1788 SDL_bool Android_JNI_IsScreenKeyboardShown()
  1789 {
  1790     JNIEnv *mEnv = Android_JNI_GetEnv();
  1791     jboolean is_shown = 0;
  1792     is_shown = (*mEnv)->CallStaticBooleanMethod(mEnv, mActivityClass, midIsScreenKeyboardShown);
  1793     return is_shown;
  1794 }
  1795 
  1796 
  1797 int Android_JNI_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid)
  1798 {
  1799     JNIEnv *env;
  1800     jclass clazz;
  1801     jmethodID mid;
  1802     jobject context;
  1803     jstring title;
  1804     jstring message;
  1805     jintArray button_flags;
  1806     jintArray button_ids;
  1807     jobjectArray button_texts;
  1808     jintArray colors;
  1809     jobject text;
  1810     jint temp;
  1811     int i;
  1812 
  1813     env = Android_JNI_GetEnv();
  1814 
  1815     /* convert parameters */
  1816 
  1817     clazz = (*env)->FindClass(env, "java/lang/String");
  1818 
  1819     title = (*env)->NewStringUTF(env, messageboxdata->title);
  1820     message = (*env)->NewStringUTF(env, messageboxdata->message);
  1821 
  1822     button_flags = (*env)->NewIntArray(env, messageboxdata->numbuttons);
  1823     button_ids = (*env)->NewIntArray(env, messageboxdata->numbuttons);
  1824     button_texts = (*env)->NewObjectArray(env, messageboxdata->numbuttons,
  1825         clazz, NULL);
  1826     for (i = 0; i < messageboxdata->numbuttons; ++i) {
  1827         temp = messageboxdata->buttons[i].flags;
  1828         (*env)->SetIntArrayRegion(env, button_flags, i, 1, &temp);
  1829         temp = messageboxdata->buttons[i].buttonid;
  1830         (*env)->SetIntArrayRegion(env, button_ids, i, 1, &temp);
  1831         text = (*env)->NewStringUTF(env, messageboxdata->buttons[i].text);
  1832         (*env)->SetObjectArrayElement(env, button_texts, i, text);
  1833         (*env)->DeleteLocalRef(env, text);
  1834     }
  1835 
  1836     if (messageboxdata->colorScheme) {
  1837         colors = (*env)->NewIntArray(env, SDL_MESSAGEBOX_COLOR_MAX);
  1838         for (i = 0; i < SDL_MESSAGEBOX_COLOR_MAX; ++i) {
  1839             temp = (0xFF << 24) |
  1840                    (messageboxdata->colorScheme->colors[i].r << 16) |
  1841                    (messageboxdata->colorScheme->colors[i].g << 8) |
  1842                    (messageboxdata->colorScheme->colors[i].b << 0);
  1843             (*env)->SetIntArrayRegion(env, colors, i, 1, &temp);
  1844         }
  1845     } else {
  1846         colors = NULL;
  1847     }
  1848 
  1849     (*env)->DeleteLocalRef(env, clazz);
  1850 
  1851     /* context = SDLActivity.getContext(); */
  1852     context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
  1853 
  1854     clazz = (*env)->GetObjectClass(env, context);
  1855 
  1856     mid = (*env)->GetMethodID(env, clazz,
  1857         "messageboxShowMessageBox", "(ILjava/lang/String;Ljava/lang/String;[I[I[Ljava/lang/String;[I)I");
  1858     *buttonid = (*env)->CallIntMethod(env, context, mid,
  1859         messageboxdata->flags,
  1860         title,
  1861         message,
  1862         button_flags,
  1863         button_ids,
  1864         button_texts,
  1865         colors);
  1866 
  1867     (*env)->DeleteLocalRef(env, context);
  1868     (*env)->DeleteLocalRef(env, clazz);
  1869 
  1870     /* delete parameters */
  1871 
  1872     (*env)->DeleteLocalRef(env, title);
  1873     (*env)->DeleteLocalRef(env, message);
  1874     (*env)->DeleteLocalRef(env, button_flags);
  1875     (*env)->DeleteLocalRef(env, button_ids);
  1876     (*env)->DeleteLocalRef(env, button_texts);
  1877     (*env)->DeleteLocalRef(env, colors);
  1878 
  1879     return 0;
  1880 }
  1881 
  1882 /*
  1883 //////////////////////////////////////////////////////////////////////////////
  1884 //
  1885 // Functions exposed to SDL applications in SDL_system.h
  1886 //////////////////////////////////////////////////////////////////////////////
  1887 */
  1888 
  1889 void *SDL_AndroidGetJNIEnv(void)
  1890 {
  1891     return Android_JNI_GetEnv();
  1892 }
  1893 
  1894 void *SDL_AndroidGetActivity(void)
  1895 {
  1896     /* See SDL_system.h for caveats on using this function. */
  1897 
  1898     JNIEnv *env = Android_JNI_GetEnv();
  1899     if (!env) {
  1900         return NULL;
  1901     }
  1902 
  1903     /* return SDLActivity.getContext(); */
  1904     return (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
  1905 }
  1906 
  1907 const char * SDL_AndroidGetInternalStoragePath(void)
  1908 {
  1909     static char *s_AndroidInternalFilesPath = NULL;
  1910 
  1911     if (!s_AndroidInternalFilesPath) {
  1912         struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  1913         jmethodID mid;
  1914         jobject context;
  1915         jobject fileObject;
  1916         jstring pathString;
  1917         const char *path;
  1918 
  1919         JNIEnv *env = Android_JNI_GetEnv();
  1920         if (!LocalReferenceHolder_Init(&refs, env)) {
  1921             LocalReferenceHolder_Cleanup(&refs);
  1922             return NULL;
  1923         }
  1924 
  1925         /* context = SDLActivity.getContext(); */
  1926         context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
  1927         if (!context) {
  1928             SDL_SetError("Couldn't get Android context!");
  1929             LocalReferenceHolder_Cleanup(&refs);
  1930             return NULL;
  1931         }
  1932 
  1933         /* fileObj = context.getFilesDir(); */
  1934         mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
  1935                 "getFilesDir", "()Ljava/io/File;");
  1936         fileObject = (*env)->CallObjectMethod(env, context, mid);
  1937         if (!fileObject) {
  1938             SDL_SetError("Couldn't get internal directory");
  1939             LocalReferenceHolder_Cleanup(&refs);
  1940             return NULL;
  1941         }
  1942 
  1943         /* path = fileObject.getAbsolutePath(); */
  1944         mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject),
  1945                 "getAbsolutePath", "()Ljava/lang/String;");
  1946         pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid);
  1947 
  1948         path = (*env)->GetStringUTFChars(env, pathString, NULL);
  1949         s_AndroidInternalFilesPath = SDL_strdup(path);
  1950         (*env)->ReleaseStringUTFChars(env, pathString, path);
  1951 
  1952         LocalReferenceHolder_Cleanup(&refs);
  1953     }
  1954     return s_AndroidInternalFilesPath;
  1955 }
  1956 
  1957 int SDL_AndroidGetExternalStorageState(void)
  1958 {
  1959     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  1960     jmethodID mid;
  1961     jclass cls;
  1962     jstring stateString;
  1963     const char *state;
  1964     int stateFlags;
  1965 
  1966     JNIEnv *env = Android_JNI_GetEnv();
  1967     if (!LocalReferenceHolder_Init(&refs, env)) {
  1968         LocalReferenceHolder_Cleanup(&refs);
  1969         return 0;
  1970     }
  1971 
  1972     cls = (*env)->FindClass(env, "android/os/Environment");
  1973     mid = (*env)->GetStaticMethodID(env, cls,
  1974             "getExternalStorageState", "()Ljava/lang/String;");
  1975     stateString = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid);
  1976 
  1977     state = (*env)->GetStringUTFChars(env, stateString, NULL);
  1978 
  1979     /* Print an info message so people debugging know the storage state */
  1980     __android_log_print(ANDROID_LOG_INFO, "SDL", "external storage state: %s", state);
  1981 
  1982     if (SDL_strcmp(state, "mounted") == 0) {
  1983         stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ |
  1984                      SDL_ANDROID_EXTERNAL_STORAGE_WRITE;
  1985     } else if (SDL_strcmp(state, "mounted_ro") == 0) {
  1986         stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ;
  1987     } else {
  1988         stateFlags = 0;
  1989     }
  1990     (*env)->ReleaseStringUTFChars(env, stateString, state);
  1991 
  1992     LocalReferenceHolder_Cleanup(&refs);
  1993     return stateFlags;
  1994 }
  1995 
  1996 const char * SDL_AndroidGetExternalStoragePath(void)
  1997 {
  1998     static char *s_AndroidExternalFilesPath = NULL;
  1999 
  2000     if (!s_AndroidExternalFilesPath) {
  2001         struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  2002         jmethodID mid;
  2003         jobject context;
  2004         jobject fileObject;
  2005         jstring pathString;
  2006         const char *path;
  2007 
  2008         JNIEnv *env = Android_JNI_GetEnv();
  2009         if (!LocalReferenceHolder_Init(&refs, env)) {
  2010             LocalReferenceHolder_Cleanup(&refs);
  2011             return NULL;
  2012         }
  2013 
  2014         /* context = SDLActivity.getContext(); */
  2015         context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
  2016 
  2017         /* fileObj = context.getExternalFilesDir(); */
  2018         mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
  2019                 "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;");
  2020         fileObject = (*env)->CallObjectMethod(env, context, mid, NULL);
  2021         if (!fileObject) {
  2022             SDL_SetError("Couldn't get external directory");
  2023             LocalReferenceHolder_Cleanup(&refs);
  2024             return NULL;
  2025         }
  2026 
  2027         /* path = fileObject.getAbsolutePath(); */
  2028         mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject),
  2029                 "getAbsolutePath", "()Ljava/lang/String;");
  2030         pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid);
  2031 
  2032         path = (*env)->GetStringUTFChars(env, pathString, NULL);
  2033         s_AndroidExternalFilesPath = SDL_strdup(path);
  2034         (*env)->ReleaseStringUTFChars(env, pathString, path);
  2035 
  2036         LocalReferenceHolder_Cleanup(&refs);
  2037     }
  2038     return s_AndroidExternalFilesPath;
  2039 }
  2040 
  2041 // Ugh, but we have to SDL_strdup() our result to pass it safely back
  2042 // out into normal SDL_getenv flow.  So we'll just do the same sort
  2043 // of trick as on Win32 over in SDL_getenv.c.
  2044 char *SDL_AndroidEnvMem;
  2045 
  2046 char *SDL_AndroidGetManifestEnvironmentVariable(const char *variableName)
  2047 {
  2048     if ((mActivityClass == NULL) || (midGetManifestEnvironmentVariable == 0)) {
  2049         __android_log_print(ANDROID_LOG_WARN, "SDL", "request to get environment variable before JNI is ready: %s", variableName);
  2050         return NULL;
  2051     }
  2052 
  2053     JNIEnv *env = Android_JNI_GetEnv();
  2054 
  2055     jstring jVariableName = (*env)->NewStringUTF(env, variableName);
  2056     jstring jResult = (jstring)((*env)->CallStaticObjectMethod(env, mActivityClass, midGetManifestEnvironmentVariable, jVariableName));
  2057 
  2058     if (jResult == NULL) {
  2059         return NULL;        
  2060     }
  2061 
  2062     if (SDL_AndroidEnvMem) {
  2063         SDL_free(SDL_AndroidEnvMem);
  2064         SDL_AndroidEnvMem = NULL;
  2065     }
  2066 
  2067     const char *result = (*env)->GetStringUTFChars(env, jResult, NULL);
  2068     SDL_AndroidEnvMem = SDL_strdup(result);
  2069     (*env)->ReleaseStringUTFChars(env, jResult, result);
  2070     (*env)->DeleteLocalRef(env, jResult);
  2071 
  2072     __android_log_print(ANDROID_LOG_INFO, "SDL", "environment variable in metadata: %s = %s", variableName, SDL_AndroidEnvMem);
  2073     return SDL_AndroidEnvMem;
  2074 }
  2075 
  2076 #endif /* __ANDROID__ */
  2077 
  2078 /* vi: set ts=4 sw=4 expandtab: */