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