src/core/android/SDL_android.c
author Sam Lantinga <slouken@libsdl.org>
Sat, 12 Aug 2017 08:15:09 -0700
changeset 11238 c728c661cec7
parent 11066 f39b145db3eb
child 11239 856e5e3c1e86
permissions -rw-r--r--
Fixed bug 3191 - haptic system on android?

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