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