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