src/core/android/SDL_android.c
author Sam Lantinga <slouken@libsdl.org>
Mon, 28 Aug 2017 10:03:39 -0700
changeset 11393 3e57ce45a890
parent 11355 6185fb86f046
child 11395 a8c29f5b679f
permissions -rw-r--r--
Fixed bug 2361 - [Android] Joysticks do not have unique IDs

David Brady

When I attempted to make a mapping file for Android gamepads, I quickly discovered that most of the ones that I have here show up as the same device (Broadcom Bluetooth HID), meaning that it was impossible to make mappings on Android, since every device looked the same.

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