src/core/android/SDL_android.c
author Philipp Wiesemann <philipp.wiesemann@arcor.de>
Sat, 03 Jun 2017 23:00:40 +0200
changeset 11061 b860259d72e5
parent 11031 b1475dc05bd0
child 11066 f39b145db3eb
permissions -rw-r--r--
android: Moved internal function to new position.

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