src/core/android/SDL_android.c
author Sam Lantinga <slouken@libsdl.org>
Sat, 04 Nov 2017 22:03:28 -0700
changeset 11688 e4d90d54cb01
parent 11678 1f10a52295e3
child 11715 35da714ed287
permissions -rw-r--r--
Fixed bug 3917 - Android, issues with getManifestEnvironmentVariable

Sylvain

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