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