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