src/core/android/SDL_android.c
author Gabriel Jacobo <gabomdq@gmail.com>
Thu, 08 Aug 2013 21:25:09 -0300
changeset 7612 627d571587d3
parent 7575 930f21eaacd7
child 7659 ac4ce59c40e7
permissions -rw-r--r--
Start Android Audio Thread after audio buffer and AudioTrack are ready.

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