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