src/core/android/SDL_android.c
author Gabriel Jacobo <gabomdq@gmail.com>
Tue, 03 Dec 2013 12:09:58 -0300
changeset 8047 a5270cef21a7
parent 8039 3b0346b37e0f
child 8055 3e2f230a6d62
permissions -rw-r--r--
[Android] Signal the resume semaphore after pushing the relevant events

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