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