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