src/core/android/SDL_android.c
author Sam Lantinga <slouken@libsdl.org>
Sat, 12 Aug 2017 12:24:59 -0700
changeset 11239 856e5e3c1e86
parent 11238 c728c661cec7
child 11270 31ce9a8ce2bd
permissions -rw-r--r--
Fixed bug 3128 - Removing all the static variables from android SDLActivity and accompanying JNI calls.

owen

I removed all the static variables from SDLActivity.java

Updated all the SDL_android.c jni calls as well

I added a new function to SDL_android.c/ h
void Android_JNI_SeparateEventsHint(const char* c);

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