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