src/core/android/SDL_android.c
author Sam Lantinga <slouken@libsdl.org>
Sun, 27 Aug 2017 18:43:52 -0700
changeset 11355 6185fb86f046
parent 11292 df399ea01ee5
child 11393 3e57ce45a890
permissions -rw-r--r--
Fixed bug 2266 - please add notifications for clipboard updates on Android

Sylvain

Hi! here's a patch for that with two class loaded regarding API level.
Test both case : before API 11 and after.

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