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