src/core/android/SDL_android.c
author Sam Lantinga <slouken@libsdl.org>
Sun, 13 Aug 2017 21:05:15 -0700
changeset 11271 ab3f2402a777
parent 11270 31ce9a8ce2bd
child 11292 df399ea01ee5
permissions -rw-r--r--
Provide the correct state of the on-screen keyboard to the API (patch from Sylvain)
     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 void Android_JNI_SetOrientation(int w, int h, int resizable, const char *hint)
   640 {
   641     jmethodID mid;
   642     JNIEnv *mEnv = Android_JNI_GetEnv();
   643     mid = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,"setOrientation","(IIZLjava/lang/String;)V");
   644     if (mid) {
   645         jstring jhint = (jstring)((*mEnv)->NewStringUTF(mEnv, (hint ? hint : "")));
   646         (*mEnv)->CallStaticVoidMethod(mEnv, mActivityClass, mid, w, h, (resizable? 1 : 0), jhint);
   647         (*mEnv)->DeleteLocalRef(mEnv, jhint);
   648     }
   649 }
   650 
   651 SDL_bool Android_JNI_GetAccelerometerValues(float values[3])
   652 {
   653     int i;
   654     SDL_bool retval = SDL_FALSE;
   655 
   656     if (bHasNewData) {
   657         for (i = 0; i < 3; ++i) {
   658             values[i] = fLastAccelerometer[i];
   659         }
   660         bHasNewData = SDL_FALSE;
   661         retval = SDL_TRUE;
   662     }
   663 
   664     return retval;
   665 }
   666 
   667 static void Android_JNI_ThreadDestroyed(void* value)
   668 {
   669     /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */
   670     JNIEnv *env = (JNIEnv*) value;
   671     if (env != NULL) {
   672         (*mJavaVM)->DetachCurrentThread(mJavaVM);
   673         pthread_setspecific(mThreadKey, NULL);
   674     }
   675 }
   676 
   677 JNIEnv* Android_JNI_GetEnv(void)
   678 {
   679     /* From http://developer.android.com/guide/practices/jni.html
   680      * All threads are Linux threads, scheduled by the kernel.
   681      * They're usually started from managed code (using Thread.start), but they can also be created elsewhere and then
   682      * attached to the JavaVM. For example, a thread started with pthread_create can be attached with the
   683      * JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a thread is attached, it has no JNIEnv,
   684      * and cannot make JNI calls.
   685      * Attaching a natively-created thread causes a java.lang.Thread object to be constructed and added to the "main"
   686      * ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread on an already-attached thread
   687      * is a no-op.
   688      * Note: You can call this function any number of times for the same thread, there's no harm in it
   689      */
   690 
   691     JNIEnv *env;
   692     int status = (*mJavaVM)->AttachCurrentThread(mJavaVM, &env, NULL);
   693     if(status < 0) {
   694         LOGE("failed to attach current thread");
   695         return 0;
   696     }
   697 
   698     /* From http://developer.android.com/guide/practices/jni.html
   699      * Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward,
   700      * in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be
   701      * called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific
   702      * to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.)
   703      * Note: The destructor is not called unless the stored value is != NULL
   704      * Note: You can call this function any number of times for the same thread, there's no harm in it
   705      *       (except for some lost CPU cycles)
   706      */
   707     pthread_setspecific(mThreadKey, (void*) env);
   708 
   709     return env;
   710 }
   711 
   712 int Android_JNI_SetupThread(void)
   713 {
   714     Android_JNI_GetEnv();
   715     return 1;
   716 }
   717 
   718 /*
   719  * Audio support
   720  */
   721 static jboolean audioBuffer16Bit = JNI_FALSE;
   722 static jobject audioBuffer = NULL;
   723 static void* audioBufferPinned = NULL;
   724 static jboolean captureBuffer16Bit = JNI_FALSE;
   725 static jobject captureBuffer = NULL;
   726 
   727 int Android_JNI_OpenAudioDevice(int iscapture, int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
   728 {
   729     jboolean audioBufferStereo;
   730     int audioBufferFrames;
   731     jobject jbufobj = NULL;
   732     jboolean isCopy;
   733 
   734     JNIEnv *env = Android_JNI_GetEnv();
   735 
   736     if (!env) {
   737         LOGE("callback_handler: failed to attach current thread");
   738     }
   739     Android_JNI_SetupThread();
   740 
   741     audioBufferStereo = channelCount > 1;
   742 
   743     if (iscapture) {
   744         __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device for capture");
   745         captureBuffer16Bit = is16Bit;
   746         if ((*env)->CallStaticIntMethod(env, mActivityClass, midCaptureOpen, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames) != 0) {
   747             /* Error during audio initialization */
   748             __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: error on AudioRecord initialization!");
   749             return 0;
   750         }
   751     } else {
   752         __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device for output");
   753         audioBuffer16Bit = is16Bit;
   754         if ((*env)->CallStaticIntMethod(env, mActivityClass, midAudioOpen, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames) != 0) {
   755             /* Error during audio initialization */
   756             __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: error on AudioTrack initialization!");
   757             return 0;
   758         }
   759     }
   760 
   761     /* Allocating the audio buffer from the Java side and passing it as the return value for audioInit no longer works on
   762      * Android >= 4.2 due to a "stale global reference" error. So now we allocate this buffer directly from this side. */
   763 
   764     if (is16Bit) {
   765         jshortArray audioBufferLocal = (*env)->NewShortArray(env, desiredBufferFrames * (audioBufferStereo ? 2 : 1));
   766         if (audioBufferLocal) {
   767             jbufobj = (*env)->NewGlobalRef(env, audioBufferLocal);
   768             (*env)->DeleteLocalRef(env, audioBufferLocal);
   769         }
   770     }
   771     else {
   772         jbyteArray audioBufferLocal = (*env)->NewByteArray(env, desiredBufferFrames * (audioBufferStereo ? 2 : 1));
   773         if (audioBufferLocal) {
   774             jbufobj = (*env)->NewGlobalRef(env, audioBufferLocal);
   775             (*env)->DeleteLocalRef(env, audioBufferLocal);
   776         }
   777     }
   778 
   779     if (jbufobj == NULL) {
   780         __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: could not allocate an audio buffer!");
   781         return 0;
   782     }
   783 
   784     if (iscapture) {
   785         captureBuffer = jbufobj;
   786     } else {
   787         audioBuffer = jbufobj;
   788     }
   789 
   790     isCopy = JNI_FALSE;
   791 
   792     if (is16Bit) {
   793         if (!iscapture) {
   794             audioBufferPinned = (*env)->GetShortArrayElements(env, (jshortArray)audioBuffer, &isCopy);
   795         }
   796         audioBufferFrames = (*env)->GetArrayLength(env, (jshortArray)audioBuffer);
   797     } else {
   798         if (!iscapture) {
   799             audioBufferPinned = (*env)->GetByteArrayElements(env, (jbyteArray)audioBuffer, &isCopy);
   800         }
   801         audioBufferFrames = (*env)->GetArrayLength(env, (jbyteArray)audioBuffer);
   802     }
   803 
   804     if (audioBufferStereo) {
   805         audioBufferFrames /= 2;
   806     }
   807 
   808     return audioBufferFrames;
   809 }
   810 
   811 void * Android_JNI_GetAudioBuffer(void)
   812 {
   813     return audioBufferPinned;
   814 }
   815 
   816 void Android_JNI_WriteAudioBuffer(void)
   817 {
   818     JNIEnv *mAudioEnv = Android_JNI_GetEnv();
   819 
   820     if (audioBuffer16Bit) {
   821         (*mAudioEnv)->ReleaseShortArrayElements(mAudioEnv, (jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
   822         (*mAudioEnv)->CallStaticVoidMethod(mAudioEnv, mActivityClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
   823     } else {
   824         (*mAudioEnv)->ReleaseByteArrayElements(mAudioEnv, (jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT);
   825         (*mAudioEnv)->CallStaticVoidMethod(mAudioEnv, mActivityClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer);
   826     }
   827 
   828     /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
   829 }
   830 
   831 int Android_JNI_CaptureAudioBuffer(void *buffer, int buflen)
   832 {
   833     JNIEnv *env = Android_JNI_GetEnv();
   834     jboolean isCopy = JNI_FALSE;
   835     jint br;
   836 
   837     if (captureBuffer16Bit) {
   838         SDL_assert((*env)->GetArrayLength(env, (jshortArray)captureBuffer) == (buflen / 2));
   839         br = (*env)->CallStaticIntMethod(env, mActivityClass, midCaptureReadShortBuffer, (jshortArray)captureBuffer, JNI_TRUE);
   840         if (br > 0) {
   841             jshort *ptr = (*env)->GetShortArrayElements(env, (jshortArray)captureBuffer, &isCopy);
   842             br *= 2;
   843             SDL_memcpy(buffer, ptr, br);
   844             (*env)->ReleaseShortArrayElements(env, (jshortArray)captureBuffer, (jshort *)ptr, JNI_ABORT);
   845         }
   846     } else {
   847         SDL_assert((*env)->GetArrayLength(env, (jshortArray)captureBuffer) == buflen);
   848         br = (*env)->CallStaticIntMethod(env, mActivityClass, midCaptureReadByteBuffer, (jbyteArray)captureBuffer, JNI_TRUE);
   849         if (br > 0) {
   850             jbyte *ptr = (*env)->GetByteArrayElements(env, (jbyteArray)captureBuffer, &isCopy);
   851             SDL_memcpy(buffer, ptr, br);
   852             (*env)->ReleaseByteArrayElements(env, (jbyteArray)captureBuffer, (jbyte *)ptr, JNI_ABORT);
   853         }
   854     }
   855 
   856     return (int) br;
   857 }
   858 
   859 void Android_JNI_FlushCapturedAudio(void)
   860 {
   861     JNIEnv *env = Android_JNI_GetEnv();
   862     #if 0  /* !!! FIXME: this needs API 23, or it'll do blocking reads and never end. */
   863     if (captureBuffer16Bit) {
   864         const jint len = (*env)->GetArrayLength(env, (jshortArray)captureBuffer);
   865         while ((*env)->CallStaticIntMethod(env, mActivityClass, midCaptureReadShortBuffer, (jshortArray)captureBuffer, JNI_FALSE) == len) { /* spin */ }
   866     } else {
   867         const jint len = (*env)->GetArrayLength(env, (jbyteArray)captureBuffer);
   868         while ((*env)->CallStaticIntMethod(env, mActivityClass, midCaptureReadByteBuffer, (jbyteArray)captureBuffer, JNI_FALSE) == len) { /* spin */ }
   869     }
   870     #else
   871     if (captureBuffer16Bit) {
   872         (*env)->CallStaticIntMethod(env, mActivityClass, midCaptureReadShortBuffer, (jshortArray)captureBuffer, JNI_FALSE);
   873     } else {
   874         (*env)->CallStaticIntMethod(env, mActivityClass, midCaptureReadByteBuffer, (jbyteArray)captureBuffer, JNI_FALSE);
   875     }
   876     #endif
   877 }
   878 
   879 void Android_JNI_CloseAudioDevice(const int iscapture)
   880 {
   881     JNIEnv *env = Android_JNI_GetEnv();
   882 
   883     if (iscapture) {
   884         (*env)->CallStaticVoidMethod(env, mActivityClass, midCaptureClose);
   885         if (captureBuffer) {
   886             (*env)->DeleteGlobalRef(env, captureBuffer);
   887             captureBuffer = NULL;
   888         }
   889     } else {
   890         (*env)->CallStaticVoidMethod(env, mActivityClass, midAudioClose);
   891         if (audioBuffer) {
   892             (*env)->DeleteGlobalRef(env, audioBuffer);
   893             audioBuffer = NULL;
   894             audioBufferPinned = NULL;
   895         }
   896     }
   897 }
   898 
   899 /* Test for an exception and call SDL_SetError with its detail if one occurs */
   900 /* If the parameter silent is truthy then SDL_SetError() will not be called. */
   901 static SDL_bool Android_JNI_ExceptionOccurred(SDL_bool silent)
   902 {
   903     JNIEnv *mEnv = Android_JNI_GetEnv();
   904     jthrowable exception;
   905 
   906     SDL_assert(LocalReferenceHolder_IsActive());
   907 
   908     exception = (*mEnv)->ExceptionOccurred(mEnv);
   909     if (exception != NULL) {
   910         jmethodID mid;
   911 
   912         /* Until this happens most JNI operations have undefined behaviour */
   913         (*mEnv)->ExceptionClear(mEnv);
   914 
   915         if (!silent) {
   916             jclass exceptionClass = (*mEnv)->GetObjectClass(mEnv, exception);
   917             jclass classClass = (*mEnv)->FindClass(mEnv, "java/lang/Class");
   918             jstring exceptionName;
   919             const char* exceptionNameUTF8;
   920             jstring exceptionMessage;
   921 
   922             mid = (*mEnv)->GetMethodID(mEnv, classClass, "getName", "()Ljava/lang/String;");
   923             exceptionName = (jstring)(*mEnv)->CallObjectMethod(mEnv, exceptionClass, mid);
   924             exceptionNameUTF8 = (*mEnv)->GetStringUTFChars(mEnv, exceptionName, 0);
   925 
   926             mid = (*mEnv)->GetMethodID(mEnv, exceptionClass, "getMessage", "()Ljava/lang/String;");
   927             exceptionMessage = (jstring)(*mEnv)->CallObjectMethod(mEnv, exception, mid);
   928 
   929             if (exceptionMessage != NULL) {
   930                 const char* exceptionMessageUTF8 = (*mEnv)->GetStringUTFChars(mEnv, exceptionMessage, 0);
   931                 SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
   932                 (*mEnv)->ReleaseStringUTFChars(mEnv, exceptionMessage, exceptionMessageUTF8);
   933             } else {
   934                 SDL_SetError("%s", exceptionNameUTF8);
   935             }
   936 
   937             (*mEnv)->ReleaseStringUTFChars(mEnv, exceptionName, exceptionNameUTF8);
   938         }
   939 
   940         return SDL_TRUE;
   941     }
   942 
   943     return SDL_FALSE;
   944 }
   945 
   946 static int Internal_Android_JNI_FileOpen(SDL_RWops* ctx)
   947 {
   948     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
   949 
   950     int result = 0;
   951 
   952     jmethodID mid;
   953     jobject context;
   954     jobject assetManager;
   955     jobject inputStream;
   956     jclass channels;
   957     jobject readableByteChannel;
   958     jstring fileNameJString;
   959     jobject fd;
   960     jclass fdCls;
   961     jfieldID descriptor;
   962 
   963     JNIEnv *mEnv = Android_JNI_GetEnv();
   964     if (!LocalReferenceHolder_Init(&refs, mEnv)) {
   965         goto failure;
   966     }
   967 
   968     fileNameJString = (jstring)ctx->hidden.androidio.fileNameRef;
   969     ctx->hidden.androidio.position = 0;
   970 
   971     /* context = SDLActivity.getContext(); */
   972     mid = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
   973             "getContext","()Landroid/content/Context;");
   974     context = (*mEnv)->CallStaticObjectMethod(mEnv, mActivityClass, mid);
   975 
   976 
   977     /* assetManager = context.getAssets(); */
   978     mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, context),
   979             "getAssets", "()Landroid/content/res/AssetManager;");
   980     assetManager = (*mEnv)->CallObjectMethod(mEnv, context, mid);
   981 
   982     /* First let's try opening the file to obtain an AssetFileDescriptor.
   983     * This method reads the files directly from the APKs using standard *nix calls
   984     */
   985     mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, assetManager), "openFd", "(Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;");
   986     inputStream = (*mEnv)->CallObjectMethod(mEnv, assetManager, mid, fileNameJString);
   987     if (Android_JNI_ExceptionOccurred(SDL_TRUE)) {
   988         goto fallback;
   989     }
   990 
   991     mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getStartOffset", "()J");
   992     ctx->hidden.androidio.offset = (*mEnv)->CallLongMethod(mEnv, inputStream, mid);
   993     if (Android_JNI_ExceptionOccurred(SDL_TRUE)) {
   994         goto fallback;
   995     }
   996 
   997     mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getDeclaredLength", "()J");
   998     ctx->hidden.androidio.size = (*mEnv)->CallLongMethod(mEnv, inputStream, mid);
   999     if (Android_JNI_ExceptionOccurred(SDL_TRUE)) {
  1000         goto fallback;
  1001     }
  1002 
  1003     mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getFileDescriptor", "()Ljava/io/FileDescriptor;");
  1004     fd = (*mEnv)->CallObjectMethod(mEnv, inputStream, mid);
  1005     fdCls = (*mEnv)->GetObjectClass(mEnv, fd);
  1006     descriptor = (*mEnv)->GetFieldID(mEnv, fdCls, "descriptor", "I");
  1007     ctx->hidden.androidio.fd = (*mEnv)->GetIntField(mEnv, fd, descriptor);
  1008     ctx->hidden.androidio.assetFileDescriptorRef = (*mEnv)->NewGlobalRef(mEnv, inputStream);
  1009 
  1010     /* Seek to the correct offset in the file. */
  1011     lseek(ctx->hidden.androidio.fd, (off_t)ctx->hidden.androidio.offset, SEEK_SET);
  1012 
  1013     if (0) {
  1014 fallback:
  1015         /* Disabled log message because of spam on the Nexus 7 */
  1016         /* __android_log_print(ANDROID_LOG_DEBUG, "SDL", "Falling back to legacy InputStream method for opening file"); */
  1017 
  1018         /* Try the old method using InputStream */
  1019         ctx->hidden.androidio.assetFileDescriptorRef = NULL;
  1020 
  1021         /* inputStream = assetManager.open(<filename>); */
  1022         mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, assetManager),
  1023                 "open", "(Ljava/lang/String;I)Ljava/io/InputStream;");
  1024         inputStream = (*mEnv)->CallObjectMethod(mEnv, assetManager, mid, fileNameJString, 1 /* ACCESS_RANDOM */);
  1025         if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
  1026             /* Try fallback to APK expansion files */
  1027             mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, context),
  1028                 "openAPKExpansionInputStream", "(Ljava/lang/String;)Ljava/io/InputStream;");
  1029             if (!mid) {
  1030                 SDL_SetError("No openAPKExpansionInputStream() in Java class");
  1031                 goto failure; /* Java class is missing the required method */
  1032             }
  1033             inputStream = (*mEnv)->CallObjectMethod(mEnv, context, mid, fileNameJString);
  1034 
  1035             /* Exception is checked first because it always needs to be cleared.
  1036              * If no exception occurred then the last SDL error message is kept.
  1037              */
  1038             if (Android_JNI_ExceptionOccurred(SDL_FALSE) || !inputStream) {
  1039                 goto failure;
  1040             }
  1041         }
  1042 
  1043         ctx->hidden.androidio.inputStreamRef = (*mEnv)->NewGlobalRef(mEnv, inputStream);
  1044 
  1045         /* Despite all the visible documentation on [Asset]InputStream claiming
  1046          * that the .available() method is not guaranteed to return the entire file
  1047          * size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
  1048          * android/apis/content/ReadAsset.java imply that Android's
  1049          * AssetInputStream.available() /will/ always return the total file size
  1050         */
  1051         
  1052         /* size = inputStream.available(); */
  1053         mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream),
  1054                 "available", "()I");
  1055         ctx->hidden.androidio.size = (long)(*mEnv)->CallIntMethod(mEnv, inputStream, mid);
  1056         if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
  1057             goto failure;
  1058         }
  1059 
  1060         /* readableByteChannel = Channels.newChannel(inputStream); */
  1061         channels = (*mEnv)->FindClass(mEnv, "java/nio/channels/Channels");
  1062         mid = (*mEnv)->GetStaticMethodID(mEnv, channels,
  1063                 "newChannel",
  1064                 "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
  1065         readableByteChannel = (*mEnv)->CallStaticObjectMethod(
  1066                 mEnv, channels, mid, inputStream);
  1067         if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
  1068             goto failure;
  1069         }
  1070 
  1071         ctx->hidden.androidio.readableByteChannelRef =
  1072             (*mEnv)->NewGlobalRef(mEnv, readableByteChannel);
  1073 
  1074         /* Store .read id for reading purposes */
  1075         mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, readableByteChannel),
  1076                 "read", "(Ljava/nio/ByteBuffer;)I");
  1077         ctx->hidden.androidio.readMethod = mid;
  1078     }
  1079 
  1080     if (0) {
  1081 failure:
  1082         result = -1;
  1083 
  1084         (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.fileNameRef);
  1085 
  1086         if(ctx->hidden.androidio.inputStreamRef != NULL) {
  1087             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.inputStreamRef);
  1088         }
  1089 
  1090         if(ctx->hidden.androidio.readableByteChannelRef != NULL) {
  1091             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.readableByteChannelRef);
  1092         }
  1093 
  1094         if(ctx->hidden.androidio.assetFileDescriptorRef != NULL) {
  1095             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.assetFileDescriptorRef);
  1096         }
  1097 
  1098     }
  1099     
  1100     LocalReferenceHolder_Cleanup(&refs);
  1101     return result;
  1102 }
  1103 
  1104 int Android_JNI_FileOpen(SDL_RWops* ctx,
  1105         const char* fileName, const char* mode)
  1106 {
  1107     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  1108     JNIEnv *mEnv = Android_JNI_GetEnv();
  1109     int retval;
  1110     jstring fileNameJString;
  1111 
  1112     if (!LocalReferenceHolder_Init(&refs, mEnv)) {
  1113         LocalReferenceHolder_Cleanup(&refs);        
  1114         return -1;
  1115     }
  1116 
  1117     if (!ctx) {
  1118         LocalReferenceHolder_Cleanup(&refs);
  1119         return -1;
  1120     }
  1121 
  1122     fileNameJString = (*mEnv)->NewStringUTF(mEnv, fileName);
  1123     ctx->hidden.androidio.fileNameRef = (*mEnv)->NewGlobalRef(mEnv, fileNameJString);
  1124     ctx->hidden.androidio.inputStreamRef = NULL;
  1125     ctx->hidden.androidio.readableByteChannelRef = NULL;
  1126     ctx->hidden.androidio.readMethod = NULL;
  1127     ctx->hidden.androidio.assetFileDescriptorRef = NULL;
  1128 
  1129     retval = Internal_Android_JNI_FileOpen(ctx);
  1130     LocalReferenceHolder_Cleanup(&refs);
  1131     return retval;
  1132 }
  1133 
  1134 size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
  1135         size_t size, size_t maxnum)
  1136 {
  1137     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  1138 
  1139     if (ctx->hidden.androidio.assetFileDescriptorRef) {
  1140         size_t bytesMax = size * maxnum;
  1141         size_t result;
  1142         if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && ctx->hidden.androidio.position + bytesMax > ctx->hidden.androidio.size) {
  1143             bytesMax = ctx->hidden.androidio.size - ctx->hidden.androidio.position;
  1144         }
  1145         result = read(ctx->hidden.androidio.fd, buffer, bytesMax );
  1146         if (result > 0) {
  1147             ctx->hidden.androidio.position += result;
  1148             LocalReferenceHolder_Cleanup(&refs);
  1149             return result / size;
  1150         }
  1151         LocalReferenceHolder_Cleanup(&refs);
  1152         return 0;
  1153     } else {
  1154         jlong bytesRemaining = (jlong) (size * maxnum);
  1155         jlong bytesMax = (jlong) (ctx->hidden.androidio.size -  ctx->hidden.androidio.position);
  1156         int bytesRead = 0;
  1157         JNIEnv *mEnv;
  1158         jobject readableByteChannel;
  1159         jmethodID readMethod;
  1160         jobject byteBuffer;
  1161 
  1162         /* Don't read more bytes than those that remain in the file, otherwise we get an exception */
  1163         if (bytesRemaining >  bytesMax) bytesRemaining = bytesMax;
  1164 
  1165         mEnv = Android_JNI_GetEnv();
  1166         if (!LocalReferenceHolder_Init(&refs, mEnv)) {
  1167             LocalReferenceHolder_Cleanup(&refs);            
  1168             return 0;
  1169         }
  1170 
  1171         readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannelRef;
  1172         readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
  1173         byteBuffer = (*mEnv)->NewDirectByteBuffer(mEnv, buffer, bytesRemaining);
  1174 
  1175         while (bytesRemaining > 0) {
  1176             /* result = readableByteChannel.read(...); */
  1177             int result = (*mEnv)->CallIntMethod(mEnv, readableByteChannel, readMethod, byteBuffer);
  1178 
  1179             if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
  1180                 LocalReferenceHolder_Cleanup(&refs);            
  1181                 return 0;
  1182             }
  1183 
  1184             if (result < 0) {
  1185                 break;
  1186             }
  1187 
  1188             bytesRemaining -= result;
  1189             bytesRead += result;
  1190             ctx->hidden.androidio.position += result;
  1191         }
  1192         LocalReferenceHolder_Cleanup(&refs);                    
  1193         return bytesRead / size;
  1194     }
  1195 }
  1196 
  1197 size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
  1198         size_t size, size_t num)
  1199 {
  1200     SDL_SetError("Cannot write to Android package filesystem");
  1201     return 0;
  1202 }
  1203 
  1204 static int Internal_Android_JNI_FileClose(SDL_RWops* ctx, SDL_bool release)
  1205 {
  1206     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  1207 
  1208     int result = 0;
  1209     JNIEnv *mEnv = Android_JNI_GetEnv();
  1210 
  1211     if (!LocalReferenceHolder_Init(&refs, mEnv)) {
  1212         LocalReferenceHolder_Cleanup(&refs);
  1213         return SDL_SetError("Failed to allocate enough JVM local references");
  1214     }
  1215 
  1216     if (ctx) {
  1217         if (release) {
  1218             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.fileNameRef);
  1219         }
  1220 
  1221         if (ctx->hidden.androidio.assetFileDescriptorRef) {
  1222             jobject inputStream = (jobject)ctx->hidden.androidio.assetFileDescriptorRef;
  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.assetFileDescriptorRef);
  1227             if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
  1228                 result = -1;
  1229             }
  1230         }
  1231         else {
  1232             jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
  1233 
  1234             /* inputStream.close(); */
  1235             jmethodID mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream),
  1236                     "close", "()V");
  1237             (*mEnv)->CallVoidMethod(mEnv, inputStream, mid);
  1238             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.inputStreamRef);
  1239             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.readableByteChannelRef);
  1240             if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
  1241                 result = -1;
  1242             }
  1243         }
  1244 
  1245         if (release) {
  1246             SDL_FreeRW(ctx);
  1247         }
  1248     }
  1249 
  1250     LocalReferenceHolder_Cleanup(&refs);
  1251     return result;
  1252 }
  1253 
  1254 
  1255 Sint64 Android_JNI_FileSize(SDL_RWops* ctx)
  1256 {
  1257     return ctx->hidden.androidio.size;
  1258 }
  1259 
  1260 Sint64 Android_JNI_FileSeek(SDL_RWops* ctx, Sint64 offset, int whence)
  1261 {
  1262     if (ctx->hidden.androidio.assetFileDescriptorRef) {
  1263         off_t ret;
  1264         switch (whence) {
  1265             case RW_SEEK_SET:
  1266                 if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
  1267                 offset += ctx->hidden.androidio.offset;
  1268                 break;
  1269             case RW_SEEK_CUR:
  1270                 offset += ctx->hidden.androidio.position;
  1271                 if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
  1272                 offset += ctx->hidden.androidio.offset;
  1273                 break;
  1274             case RW_SEEK_END:
  1275                 offset = ctx->hidden.androidio.offset + ctx->hidden.androidio.size + offset;
  1276                 break;
  1277             default:
  1278                 return SDL_SetError("Unknown value for 'whence'");
  1279         }
  1280         whence = SEEK_SET;
  1281 
  1282         ret = lseek(ctx->hidden.androidio.fd, (off_t)offset, SEEK_SET);
  1283         if (ret == -1) return -1;
  1284         ctx->hidden.androidio.position = ret - ctx->hidden.androidio.offset;
  1285     } else {
  1286         Sint64 newPosition;
  1287         Sint64 movement;
  1288 
  1289         switch (whence) {
  1290             case RW_SEEK_SET:
  1291                 newPosition = offset;
  1292                 break;
  1293             case RW_SEEK_CUR:
  1294                 newPosition = ctx->hidden.androidio.position + offset;
  1295                 break;
  1296             case RW_SEEK_END:
  1297                 newPosition = ctx->hidden.androidio.size + offset;
  1298                 break;
  1299             default:
  1300                 return SDL_SetError("Unknown value for 'whence'");
  1301         }
  1302 
  1303         /* Validate the new position */
  1304         if (newPosition < 0) {
  1305             return SDL_Error(SDL_EFSEEK);
  1306         }
  1307         if (newPosition > ctx->hidden.androidio.size) {
  1308             newPosition = ctx->hidden.androidio.size;
  1309         }
  1310 
  1311         movement = newPosition - ctx->hidden.androidio.position;
  1312         if (movement > 0) {
  1313             unsigned char buffer[4096];
  1314 
  1315             /* The easy case where we're seeking forwards */
  1316             while (movement > 0) {
  1317                 Sint64 amount = sizeof (buffer);
  1318                 size_t result;
  1319                 if (amount > movement) {
  1320                     amount = movement;
  1321                 }
  1322                 result = Android_JNI_FileRead(ctx, buffer, 1, amount);
  1323                 if (result <= 0) {
  1324                     /* Failed to read/skip the required amount, so fail */
  1325                     return -1;
  1326                 }
  1327 
  1328                 movement -= result;
  1329             }
  1330 
  1331         } else if (movement < 0) {
  1332             /* We can't seek backwards so we have to reopen the file and seek */
  1333             /* forwards which obviously isn't very efficient */
  1334             Internal_Android_JNI_FileClose(ctx, SDL_FALSE);
  1335             Internal_Android_JNI_FileOpen(ctx);
  1336             Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
  1337         }
  1338     }
  1339 
  1340     return ctx->hidden.androidio.position;
  1341 
  1342 }
  1343 
  1344 int Android_JNI_FileClose(SDL_RWops* ctx)
  1345 {
  1346     return Internal_Android_JNI_FileClose(ctx, SDL_TRUE);
  1347 }
  1348 
  1349 /* returns a new global reference which needs to be released later */
  1350 static jobject Android_JNI_GetSystemServiceObject(const char* name)
  1351 {
  1352     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  1353     JNIEnv* env = Android_JNI_GetEnv();
  1354     jobject retval = NULL;
  1355     jstring service;
  1356     jmethodID mid;
  1357     jobject context;
  1358     jobject manager;
  1359 
  1360     if (!LocalReferenceHolder_Init(&refs, env)) {
  1361         LocalReferenceHolder_Cleanup(&refs);
  1362         return NULL;
  1363     }
  1364 
  1365     service = (*env)->NewStringUTF(env, name);
  1366 
  1367     mid = (*env)->GetStaticMethodID(env, mActivityClass, "getContext", "()Landroid/content/Context;");
  1368     context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
  1369 
  1370     mid = (*env)->GetMethodID(env, mActivityClass, "getSystemServiceFromUiThread", "(Ljava/lang/String;)Ljava/lang/Object;");
  1371     manager = (*env)->CallObjectMethod(env, context, mid, service);
  1372 
  1373     (*env)->DeleteLocalRef(env, service);
  1374 
  1375     retval = manager ? (*env)->NewGlobalRef(env, manager) : NULL;
  1376     LocalReferenceHolder_Cleanup(&refs);
  1377     return retval;
  1378 }
  1379 
  1380 #define SETUP_CLIPBOARD(error) \
  1381     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); \
  1382     JNIEnv* env = Android_JNI_GetEnv(); \
  1383     jobject clipboard; \
  1384     if (!LocalReferenceHolder_Init(&refs, env)) { \
  1385         LocalReferenceHolder_Cleanup(&refs); \
  1386         return error; \
  1387     } \
  1388     clipboard = Android_JNI_GetSystemServiceObject("clipboard"); \
  1389     if (!clipboard) { \
  1390         LocalReferenceHolder_Cleanup(&refs); \
  1391         return error; \
  1392     }
  1393 
  1394 #define CLEANUP_CLIPBOARD() \
  1395     LocalReferenceHolder_Cleanup(&refs);
  1396 
  1397 int Android_JNI_SetClipboardText(const char* text)
  1398 {
  1399     /* Watch out for C89 scoping rules because of the macro */
  1400     SETUP_CLIPBOARD(-1)
  1401 
  1402     /* Nest the following in a scope to avoid C89 declaration rules triggered by the macro */
  1403     {
  1404         jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, clipboard), "setText", "(Ljava/lang/CharSequence;)V");
  1405         jstring string = (*env)->NewStringUTF(env, text);
  1406         (*env)->CallVoidMethod(env, clipboard, mid, string);
  1407         (*env)->DeleteGlobalRef(env, clipboard);
  1408         (*env)->DeleteLocalRef(env, string);
  1409     }
  1410     CLEANUP_CLIPBOARD();
  1411 
  1412     return 0;
  1413 }
  1414 
  1415 char* Android_JNI_GetClipboardText(void)
  1416 {
  1417     /* Watch out for C89 scoping rules because of the macro */
  1418     SETUP_CLIPBOARD(SDL_strdup(""))
  1419 
  1420     /* Nest the following in a scope to avoid C89 declaration rules triggered by the macro */
  1421     {
  1422         jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, clipboard), "getText", "()Ljava/lang/CharSequence;");
  1423         jobject sequence = (*env)->CallObjectMethod(env, clipboard, mid);
  1424         (*env)->DeleteGlobalRef(env, clipboard);
  1425         if (sequence) {
  1426             jstring string;
  1427             const char* utf;
  1428             mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, sequence), "toString", "()Ljava/lang/String;");
  1429             string = (jstring)((*env)->CallObjectMethod(env, sequence, mid));
  1430             utf = (*env)->GetStringUTFChars(env, string, 0);
  1431             if (utf) {
  1432                 char* text = SDL_strdup(utf);
  1433                 (*env)->ReleaseStringUTFChars(env, string, utf);
  1434 
  1435                 CLEANUP_CLIPBOARD();
  1436 
  1437                 return text;
  1438             }
  1439         }
  1440     }
  1441     CLEANUP_CLIPBOARD();    
  1442 
  1443     return SDL_strdup("");
  1444 }
  1445 
  1446 SDL_bool Android_JNI_HasClipboardText(void)
  1447 {
  1448     jmethodID mid;
  1449     jboolean has;
  1450     /* Watch out for C89 scoping rules because of the macro */
  1451     SETUP_CLIPBOARD(SDL_FALSE)
  1452 
  1453     mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, clipboard), "hasText", "()Z");
  1454     has = (*env)->CallBooleanMethod(env, clipboard, mid);
  1455     (*env)->DeleteGlobalRef(env, clipboard);
  1456 
  1457     CLEANUP_CLIPBOARD();
  1458     
  1459     return has ? SDL_TRUE : SDL_FALSE;
  1460 }
  1461 
  1462 
  1463 /* returns 0 on success or -1 on error (others undefined then)
  1464  * returns truthy or falsy value in plugged, charged and battery
  1465  * returns the value in seconds and percent or -1 if not available
  1466  */
  1467 int Android_JNI_GetPowerInfo(int* plugged, int* charged, int* battery, int* seconds, int* percent)
  1468 {
  1469     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  1470     JNIEnv* env = Android_JNI_GetEnv();
  1471     jmethodID mid;
  1472     jobject context;
  1473     jstring action;
  1474     jclass cls;
  1475     jobject filter;
  1476     jobject intent;
  1477     jstring iname;
  1478     jmethodID imid;
  1479     jstring bname;
  1480     jmethodID bmid;
  1481     if (!LocalReferenceHolder_Init(&refs, env)) {
  1482         LocalReferenceHolder_Cleanup(&refs);
  1483         return -1;
  1484     }
  1485 
  1486 
  1487     mid = (*env)->GetStaticMethodID(env, mActivityClass, "getContext", "()Landroid/content/Context;");
  1488     context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
  1489 
  1490     action = (*env)->NewStringUTF(env, "android.intent.action.BATTERY_CHANGED");
  1491 
  1492     cls = (*env)->FindClass(env, "android/content/IntentFilter");
  1493 
  1494     mid = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/lang/String;)V");
  1495     filter = (*env)->NewObject(env, cls, mid, action);
  1496 
  1497     (*env)->DeleteLocalRef(env, action);
  1498 
  1499     mid = (*env)->GetMethodID(env, mActivityClass, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;");
  1500     intent = (*env)->CallObjectMethod(env, context, mid, NULL, filter);
  1501 
  1502     (*env)->DeleteLocalRef(env, filter);
  1503 
  1504     cls = (*env)->GetObjectClass(env, intent);
  1505 
  1506     imid = (*env)->GetMethodID(env, cls, "getIntExtra", "(Ljava/lang/String;I)I");
  1507 
  1508     /* Watch out for C89 scoping rules because of the macro */
  1509 #define GET_INT_EXTRA(var, key) \
  1510     int var; \
  1511     iname = (*env)->NewStringUTF(env, key); \
  1512     var = (*env)->CallIntMethod(env, intent, imid, iname, -1); \
  1513     (*env)->DeleteLocalRef(env, iname);
  1514 
  1515     bmid = (*env)->GetMethodID(env, cls, "getBooleanExtra", "(Ljava/lang/String;Z)Z");
  1516 
  1517     /* Watch out for C89 scoping rules because of the macro */
  1518 #define GET_BOOL_EXTRA(var, key) \
  1519     int var; \
  1520     bname = (*env)->NewStringUTF(env, key); \
  1521     var = (*env)->CallBooleanMethod(env, intent, bmid, bname, JNI_FALSE); \
  1522     (*env)->DeleteLocalRef(env, bname);
  1523 
  1524     if (plugged) {
  1525         /* Watch out for C89 scoping rules because of the macro */
  1526         GET_INT_EXTRA(plug, "plugged") /* == BatteryManager.EXTRA_PLUGGED (API 5) */
  1527         if (plug == -1) {
  1528             LocalReferenceHolder_Cleanup(&refs);
  1529             return -1;
  1530         }
  1531         /* 1 == BatteryManager.BATTERY_PLUGGED_AC */
  1532         /* 2 == BatteryManager.BATTERY_PLUGGED_USB */
  1533         *plugged = (0 < plug) ? 1 : 0;
  1534     }
  1535 
  1536     if (charged) {
  1537         /* Watch out for C89 scoping rules because of the macro */
  1538         GET_INT_EXTRA(status, "status") /* == BatteryManager.EXTRA_STATUS (API 5) */
  1539         if (status == -1) {
  1540             LocalReferenceHolder_Cleanup(&refs);
  1541             return -1;
  1542         }
  1543         /* 5 == BatteryManager.BATTERY_STATUS_FULL */
  1544         *charged = (status == 5) ? 1 : 0;
  1545     }
  1546 
  1547     if (battery) {
  1548         GET_BOOL_EXTRA(present, "present") /* == BatteryManager.EXTRA_PRESENT (API 5) */
  1549         *battery = present ? 1 : 0;
  1550     }
  1551 
  1552     if (seconds) {
  1553         *seconds = -1; /* not possible */
  1554     }
  1555 
  1556     if (percent) {
  1557         int level;
  1558         int scale;
  1559         
  1560         /* Watch out for C89 scoping rules because of the macro */
  1561         {
  1562             GET_INT_EXTRA(level_temp, "level") /* == BatteryManager.EXTRA_LEVEL (API 5) */
  1563             level = level_temp;
  1564         }
  1565         /* Watch out for C89 scoping rules because of the macro */
  1566         {
  1567             GET_INT_EXTRA(scale_temp, "scale") /* == BatteryManager.EXTRA_SCALE (API 5) */
  1568             scale = scale_temp;
  1569         }
  1570         
  1571         if ((level == -1) || (scale == -1)) {
  1572             LocalReferenceHolder_Cleanup(&refs);
  1573             return -1;
  1574         }
  1575         *percent = level * 100 / scale;
  1576     }
  1577 
  1578     (*env)->DeleteLocalRef(env, intent);
  1579 
  1580     LocalReferenceHolder_Cleanup(&refs);
  1581     return 0;
  1582 }
  1583 
  1584 /* returns number of found touch devices as return value and ids in parameter ids */
  1585 int Android_JNI_GetTouchDeviceIds(int **ids) {
  1586     JNIEnv *env = Android_JNI_GetEnv();
  1587     jint sources = 4098; /* == InputDevice.SOURCE_TOUCHSCREEN */
  1588     jmethodID mid = (*env)->GetStaticMethodID(env, mActivityClass, "inputGetInputDeviceIds", "(I)[I");
  1589     jintArray array = (jintArray) (*env)->CallStaticObjectMethod(env, mActivityClass, mid, sources);
  1590     int number = 0;
  1591     *ids = NULL;
  1592     if (array) {
  1593         number = (int) (*env)->GetArrayLength(env, array);
  1594         if (0 < number) {
  1595             jint* elements = (*env)->GetIntArrayElements(env, array, NULL);
  1596             if (elements) {
  1597                 int i;
  1598                 *ids = SDL_malloc(number * sizeof (**ids));
  1599                 for (i = 0; i < number; ++i) { /* not assuming sizeof (jint) == sizeof (int) */
  1600                     (*ids)[i] = elements[i];
  1601                 }
  1602                 (*env)->ReleaseIntArrayElements(env, array, elements, JNI_ABORT);
  1603             }
  1604         }
  1605         (*env)->DeleteLocalRef(env, array);
  1606     }
  1607     return number;
  1608 }
  1609 
  1610 /* sets the mSeparateMouseAndTouch field */
  1611 void Android_JNI_SetSeparateMouseAndTouch(SDL_bool new_value)
  1612 {
  1613     JNIEnv *env = Android_JNI_GetEnv();
  1614     (*env)->SetStaticBooleanField(env, mActivityClass, fidSeparateMouseAndTouch, new_value ? JNI_TRUE : JNI_FALSE);
  1615 }
  1616 
  1617 void Android_JNI_PollInputDevices(void)
  1618 {
  1619     JNIEnv *env = Android_JNI_GetEnv();
  1620     (*env)->CallStaticVoidMethod(env, mActivityClass, midPollInputDevices);    
  1621 }
  1622 
  1623 void Android_JNI_PollHapticDevices(void)
  1624 {
  1625     JNIEnv *env = Android_JNI_GetEnv();
  1626     (*env)->CallStaticVoidMethod(env, mActivityClass, midPollHapticDevices);
  1627 }
  1628     
  1629 void Android_JNI_HapticRun(int device_id, int length)
  1630 {
  1631     JNIEnv *env = Android_JNI_GetEnv();
  1632     (*env)->CallStaticVoidMethod(env, mActivityClass, midHapticRun, device_id, length);
  1633 }
  1634 
  1635 
  1636 /* See SDLActivity.java for constants. */
  1637 #define COMMAND_SET_KEEP_SCREEN_ON    5
  1638 
  1639 /* sends message to be handled on the UI event dispatch thread */
  1640 int Android_JNI_SendMessage(int command, int param)
  1641 {
  1642     JNIEnv *env = Android_JNI_GetEnv();
  1643     jmethodID mid;
  1644     jboolean success;
  1645     if (!env) {
  1646         return -1;
  1647     }
  1648     mid = (*env)->GetStaticMethodID(env, mActivityClass, "sendMessage", "(II)Z");
  1649     if (!mid) {
  1650         return -1;
  1651     }
  1652     success = (*env)->CallStaticBooleanMethod(env, mActivityClass, mid, command, param);
  1653     return success ? 0 : -1;
  1654 }
  1655 
  1656 void Android_JNI_SuspendScreenSaver(SDL_bool suspend)
  1657 {
  1658     Android_JNI_SendMessage(COMMAND_SET_KEEP_SCREEN_ON, (suspend == SDL_FALSE) ? 0 : 1);
  1659 }
  1660 
  1661 void Android_JNI_ShowTextInput(SDL_Rect *inputRect)
  1662 {
  1663     JNIEnv *env = Android_JNI_GetEnv();
  1664     jmethodID mid;
  1665     if (!env) {
  1666         return;
  1667     }
  1668 
  1669     mid = (*env)->GetStaticMethodID(env, mActivityClass, "showTextInput", "(IIII)Z");
  1670     if (!mid) {
  1671         return;
  1672     }
  1673     (*env)->CallStaticBooleanMethod(env, mActivityClass, mid,
  1674                                inputRect->x,
  1675                                inputRect->y,
  1676                                inputRect->w,
  1677                                inputRect->h );
  1678 }
  1679 
  1680 void Android_JNI_HideTextInput(void)
  1681 {
  1682     /* has to match Activity constant */
  1683     const int COMMAND_TEXTEDIT_HIDE = 3;
  1684     Android_JNI_SendMessage(COMMAND_TEXTEDIT_HIDE, 0);
  1685 }
  1686 
  1687 SDL_bool Android_JNI_IsScreenKeyboardShown()
  1688 {
  1689     jmethodID mid;
  1690     jboolean is_shown = 0;
  1691     JNIEnv *mEnv = Android_JNI_GetEnv();
  1692     mid = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,"isScreenKeyboardShown","()Z");
  1693     if (mid) {
  1694         is_shown = (*mEnv)->CallStaticBooleanMethod(mEnv, mActivityClass, mid);
  1695     }
  1696 
  1697     return is_shown;
  1698 }
  1699 
  1700 
  1701 int Android_JNI_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid)
  1702 {
  1703     JNIEnv *env;
  1704     jclass clazz;
  1705     jmethodID mid;
  1706     jobject context;
  1707     jstring title;
  1708     jstring message;
  1709     jintArray button_flags;
  1710     jintArray button_ids;
  1711     jobjectArray button_texts;
  1712     jintArray colors;
  1713     jobject text;
  1714     jint temp;
  1715     int i;
  1716 
  1717     env = Android_JNI_GetEnv();
  1718 
  1719     /* convert parameters */
  1720 
  1721     clazz = (*env)->FindClass(env, "java/lang/String");
  1722 
  1723     title = (*env)->NewStringUTF(env, messageboxdata->title);
  1724     message = (*env)->NewStringUTF(env, messageboxdata->message);
  1725 
  1726     button_flags = (*env)->NewIntArray(env, messageboxdata->numbuttons);
  1727     button_ids = (*env)->NewIntArray(env, messageboxdata->numbuttons);
  1728     button_texts = (*env)->NewObjectArray(env, messageboxdata->numbuttons,
  1729         clazz, NULL);
  1730     for (i = 0; i < messageboxdata->numbuttons; ++i) {
  1731         temp = messageboxdata->buttons[i].flags;
  1732         (*env)->SetIntArrayRegion(env, button_flags, i, 1, &temp);
  1733         temp = messageboxdata->buttons[i].buttonid;
  1734         (*env)->SetIntArrayRegion(env, button_ids, i, 1, &temp);
  1735         text = (*env)->NewStringUTF(env, messageboxdata->buttons[i].text);
  1736         (*env)->SetObjectArrayElement(env, button_texts, i, text);
  1737         (*env)->DeleteLocalRef(env, text);
  1738     }
  1739 
  1740     if (messageboxdata->colorScheme) {
  1741         colors = (*env)->NewIntArray(env, SDL_MESSAGEBOX_COLOR_MAX);
  1742         for (i = 0; i < SDL_MESSAGEBOX_COLOR_MAX; ++i) {
  1743             temp = (0xFF << 24) |
  1744                    (messageboxdata->colorScheme->colors[i].r << 16) |
  1745                    (messageboxdata->colorScheme->colors[i].g << 8) |
  1746                    (messageboxdata->colorScheme->colors[i].b << 0);
  1747             (*env)->SetIntArrayRegion(env, colors, i, 1, &temp);
  1748         }
  1749     } else {
  1750         colors = NULL;
  1751     }
  1752 
  1753     (*env)->DeleteLocalRef(env, clazz);
  1754 
  1755     /* call function */
  1756 
  1757     mid = (*env)->GetStaticMethodID(env, mActivityClass, "getContext","()Landroid/content/Context;");
  1758 
  1759     context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
  1760 
  1761     clazz = (*env)->GetObjectClass(env, context);
  1762 
  1763     mid = (*env)->GetMethodID(env, clazz,
  1764         "messageboxShowMessageBox", "(ILjava/lang/String;Ljava/lang/String;[I[I[Ljava/lang/String;[I)I");
  1765     *buttonid = (*env)->CallIntMethod(env, context, mid,
  1766         messageboxdata->flags,
  1767         title,
  1768         message,
  1769         button_flags,
  1770         button_ids,
  1771         button_texts,
  1772         colors);
  1773 
  1774     (*env)->DeleteLocalRef(env, context);
  1775     (*env)->DeleteLocalRef(env, clazz);
  1776 
  1777     /* delete parameters */
  1778 
  1779     (*env)->DeleteLocalRef(env, title);
  1780     (*env)->DeleteLocalRef(env, message);
  1781     (*env)->DeleteLocalRef(env, button_flags);
  1782     (*env)->DeleteLocalRef(env, button_ids);
  1783     (*env)->DeleteLocalRef(env, button_texts);
  1784     (*env)->DeleteLocalRef(env, colors);
  1785 
  1786     return 0;
  1787 }
  1788 
  1789 /*
  1790 //////////////////////////////////////////////////////////////////////////////
  1791 //
  1792 // Functions exposed to SDL applications in SDL_system.h
  1793 //////////////////////////////////////////////////////////////////////////////
  1794 */
  1795 
  1796 void *SDL_AndroidGetJNIEnv(void)
  1797 {
  1798     return Android_JNI_GetEnv();
  1799 }
  1800 
  1801 void *SDL_AndroidGetActivity(void)
  1802 {
  1803     /* See SDL_system.h for caveats on using this function. */
  1804 
  1805     jmethodID mid;
  1806 
  1807     JNIEnv *env = Android_JNI_GetEnv();
  1808     if (!env) {
  1809         return NULL;
  1810     }
  1811 
  1812     /* return SDLActivity.getContext(); */
  1813     mid = (*env)->GetStaticMethodID(env, mActivityClass,
  1814             "getContext","()Landroid/content/Context;");
  1815     return (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
  1816 }
  1817 
  1818 const char * SDL_AndroidGetInternalStoragePath(void)
  1819 {
  1820     static char *s_AndroidInternalFilesPath = NULL;
  1821 
  1822     if (!s_AndroidInternalFilesPath) {
  1823         struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  1824         jmethodID mid;
  1825         jobject context;
  1826         jobject fileObject;
  1827         jstring pathString;
  1828         const char *path;
  1829 
  1830         JNIEnv *env = Android_JNI_GetEnv();
  1831         if (!LocalReferenceHolder_Init(&refs, env)) {
  1832             LocalReferenceHolder_Cleanup(&refs);
  1833             return NULL;
  1834         }
  1835 
  1836         /* context = SDLActivity.getContext(); */
  1837         mid = (*env)->GetStaticMethodID(env, mActivityClass,
  1838                 "getContext","()Landroid/content/Context;");
  1839         context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
  1840 
  1841         /* fileObj = context.getFilesDir(); */
  1842         mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
  1843                 "getFilesDir", "()Ljava/io/File;");
  1844         fileObject = (*env)->CallObjectMethod(env, context, mid);
  1845         if (!fileObject) {
  1846             SDL_SetError("Couldn't get internal directory");
  1847             LocalReferenceHolder_Cleanup(&refs);
  1848             return NULL;
  1849         }
  1850 
  1851         /* path = fileObject.getAbsolutePath(); */
  1852         mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject),
  1853                 "getAbsolutePath", "()Ljava/lang/String;");
  1854         pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid);
  1855 
  1856         path = (*env)->GetStringUTFChars(env, pathString, NULL);
  1857         s_AndroidInternalFilesPath = SDL_strdup(path);
  1858         (*env)->ReleaseStringUTFChars(env, pathString, path);
  1859 
  1860         LocalReferenceHolder_Cleanup(&refs);
  1861     }
  1862     return s_AndroidInternalFilesPath;
  1863 }
  1864 
  1865 int SDL_AndroidGetExternalStorageState(void)
  1866 {
  1867     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  1868     jmethodID mid;
  1869     jclass cls;
  1870     jstring stateString;
  1871     const char *state;
  1872     int stateFlags;
  1873 
  1874     JNIEnv *env = Android_JNI_GetEnv();
  1875     if (!LocalReferenceHolder_Init(&refs, env)) {
  1876         LocalReferenceHolder_Cleanup(&refs);
  1877         return 0;
  1878     }
  1879 
  1880     cls = (*env)->FindClass(env, "android/os/Environment");
  1881     mid = (*env)->GetStaticMethodID(env, cls,
  1882             "getExternalStorageState", "()Ljava/lang/String;");
  1883     stateString = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid);
  1884 
  1885     state = (*env)->GetStringUTFChars(env, stateString, NULL);
  1886 
  1887     /* Print an info message so people debugging know the storage state */
  1888     __android_log_print(ANDROID_LOG_INFO, "SDL", "external storage state: %s", state);
  1889 
  1890     if (SDL_strcmp(state, "mounted") == 0) {
  1891         stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ |
  1892                      SDL_ANDROID_EXTERNAL_STORAGE_WRITE;
  1893     } else if (SDL_strcmp(state, "mounted_ro") == 0) {
  1894         stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ;
  1895     } else {
  1896         stateFlags = 0;
  1897     }
  1898     (*env)->ReleaseStringUTFChars(env, stateString, state);
  1899 
  1900     LocalReferenceHolder_Cleanup(&refs);
  1901     return stateFlags;
  1902 }
  1903 
  1904 const char * SDL_AndroidGetExternalStoragePath(void)
  1905 {
  1906     static char *s_AndroidExternalFilesPath = NULL;
  1907 
  1908     if (!s_AndroidExternalFilesPath) {
  1909         struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  1910         jmethodID mid;
  1911         jobject context;
  1912         jobject fileObject;
  1913         jstring pathString;
  1914         const char *path;
  1915 
  1916         JNIEnv *env = Android_JNI_GetEnv();
  1917         if (!LocalReferenceHolder_Init(&refs, env)) {
  1918             LocalReferenceHolder_Cleanup(&refs);
  1919             return NULL;
  1920         }
  1921 
  1922         /* context = SDLActivity.getContext(); */
  1923         mid = (*env)->GetStaticMethodID(env, mActivityClass,
  1924                 "getContext","()Landroid/content/Context;");
  1925         context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
  1926 
  1927         /* fileObj = context.getExternalFilesDir(); */
  1928         mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
  1929                 "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;");
  1930         fileObject = (*env)->CallObjectMethod(env, context, mid, NULL);
  1931         if (!fileObject) {
  1932             SDL_SetError("Couldn't get external directory");
  1933             LocalReferenceHolder_Cleanup(&refs);
  1934             return NULL;
  1935         }
  1936 
  1937         /* path = fileObject.getAbsolutePath(); */
  1938         mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject),
  1939                 "getAbsolutePath", "()Ljava/lang/String;");
  1940         pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid);
  1941 
  1942         path = (*env)->GetStringUTFChars(env, pathString, NULL);
  1943         s_AndroidExternalFilesPath = SDL_strdup(path);
  1944         (*env)->ReleaseStringUTFChars(env, pathString, path);
  1945 
  1946         LocalReferenceHolder_Cleanup(&refs);
  1947     }
  1948     return s_AndroidExternalFilesPath;
  1949 }
  1950 
  1951 #endif /* __ANDROID__ */
  1952 
  1953 /* vi: set ts=4 sw=4 expandtab: */