src/core/android/SDL_android.c
author Sam Lantinga <slouken@libsdl.org>
Wed, 01 Nov 2017 17:30:02 -0700
changeset 11665 861f42ecf09a
parent 11662 a996f135cc81
child 11669 1fe21a20aa12
permissions -rw-r--r--
Fixed bug 3932 - Android, GetDisplayDPI release local reference

Sylvain

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