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