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