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