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