src/core/android/SDL_android.c
author Sam Lantinga <slouken@libsdl.org>
Thu, 07 Jun 2018 17:07:03 -0700
changeset 12012 6de756c9975a
parent 12008 91f9b8f22b17
child 12024 3688283680b1
permissions -rw-r--r--
Track android device panel width & height as well as window surface & height.

Expand SDLActivity::SDLSurface::surfaceChanged() callback to grab the panel width and height at the same time and pass that along to the native code. Only works on API 17+. Duplicates surface dimensions whenever it fails.

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