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