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