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