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