src/core/android/SDL_android.c
author Gabriel Jacobo <gabomdq@gmail.com>
Fri, 02 Aug 2013 12:38:39 -0300
changeset 7564 d2bc997ef5d6
parent 7552 463f78515a07
child 7567 52da75545aaa
permissions -rw-r--r--
Temporary fix for bug #1639 SDL does not message back closing of screen keyboard

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