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