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