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