src/core/android/SDL_android.cpp
author Gabriel Jacobo <gabomdq@gmail.com>
Mon, 14 Jan 2013 20:20:33 -0300
changeset 6816 b3d3ef1e15b5
parent 6806 9e57ff36fd7a
child 6828 ac7f004fb63c
permissions -rw-r--r--
Fixes bug 1696, thanks Paul Geerts!
Also adds a bit of information about asset loading on Android.
     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     ctx->hidden.androidio.assetFileDescriptorRef = mEnv->NewGlobalRef(inputStream);
   597     mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream), "getStartOffset", "()J");
   598     ctx->hidden.androidio.offset = mEnv->CallLongMethod(inputStream, mid);
   599     if (Android_JNI_ExceptionOccurred()) {
   600         goto fallback;
   601     }
   602 
   603     mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream), "getDeclaredLength", "()J");
   604     ctx->hidden.androidio.size = mEnv->CallLongMethod(inputStream, mid);
   605     
   606     if (Android_JNI_ExceptionOccurred()) {
   607         goto fallback;
   608     }
   609 
   610     mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream), "getFileDescriptor", "()Ljava/io/FileDescriptor;");
   611     fd = mEnv->CallObjectMethod(inputStream, mid);
   612     fdCls = mEnv->GetObjectClass(fd);
   613     descriptor = mEnv->GetFieldID(fdCls, "descriptor", "I");
   614     ctx->hidden.androidio.fd = mEnv->GetIntField(fd, descriptor);
   615 
   616     // Seek to the correct offset in the file.
   617     lseek(ctx->hidden.androidio.fd, (off_t)ctx->hidden.androidio.offset, SEEK_SET);
   618 
   619     if (false) {
   620 fallback:
   621         __android_log_print(ANDROID_LOG_DEBUG, "SDL", "Falling back to legacy InputStream method for opening file");
   622         /* Try the old method using InputStream */
   623         ctx->hidden.androidio.assetFileDescriptorRef = NULL;
   624 
   625         // inputStream = assetManager.open(<filename>);
   626         mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager),
   627                 "open", "(Ljava/lang/String;I)Ljava/io/InputStream;");
   628         inputStream = mEnv->CallObjectMethod(assetManager, mid, fileNameJString, 1 /*ACCESS_RANDOM*/);
   629         if (Android_JNI_ExceptionOccurred()) {
   630             goto failure;
   631         }
   632 
   633         ctx->hidden.androidio.inputStreamRef = mEnv->NewGlobalRef(inputStream);
   634 
   635         // Despite all the visible documentation on [Asset]InputStream claiming
   636         // that the .available() method is not guaranteed to return the entire file
   637         // size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
   638         // android/apis/content/ReadAsset.java imply that Android's
   639         // AssetInputStream.available() /will/ always return the total file size
   640 
   641         // size = inputStream.available();
   642         mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   643                 "available", "()I");
   644         ctx->hidden.androidio.size = (long)mEnv->CallIntMethod(inputStream, mid);
   645         if (Android_JNI_ExceptionOccurred()) {
   646             goto failure;
   647         }
   648 
   649         // readableByteChannel = Channels.newChannel(inputStream);
   650         channels = mEnv->FindClass("java/nio/channels/Channels");
   651         mid = mEnv->GetStaticMethodID(channels,
   652                 "newChannel",
   653                 "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
   654         readableByteChannel = mEnv->CallStaticObjectMethod(
   655                 channels, mid, inputStream);
   656         if (Android_JNI_ExceptionOccurred()) {
   657             goto failure;
   658         }
   659 
   660         ctx->hidden.androidio.readableByteChannelRef =
   661             mEnv->NewGlobalRef(readableByteChannel);
   662 
   663         // Store .read id for reading purposes
   664         mid = mEnv->GetMethodID(mEnv->GetObjectClass(readableByteChannel),
   665                 "read", "(Ljava/nio/ByteBuffer;)I");
   666         ctx->hidden.androidio.readMethod = mid;
   667     }
   668 
   669     if (false) {
   670 failure:
   671         result = -1;
   672 
   673         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   674 
   675         if(ctx->hidden.androidio.inputStreamRef != NULL) {
   676             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   677         }
   678 
   679         if(ctx->hidden.androidio.readableByteChannelRef != NULL) {
   680             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
   681         }
   682 
   683         if(ctx->hidden.androidio.assetFileDescriptorRef != NULL) {
   684             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.assetFileDescriptorRef);
   685         }
   686 
   687     }
   688 
   689     return result;
   690 }
   691 
   692 extern "C" int Android_JNI_FileOpen(SDL_RWops* ctx,
   693         const char* fileName, const char*)
   694 {
   695     LocalReferenceHolder refs(__FUNCTION__);
   696     JNIEnv *mEnv = Android_JNI_GetEnv();
   697 
   698     if (!refs.init(mEnv)) {
   699         return -1;
   700     }
   701 
   702     if (!ctx) {
   703         return -1;
   704     }
   705 
   706     jstring fileNameJString = mEnv->NewStringUTF(fileName);
   707     ctx->hidden.androidio.fileNameRef = mEnv->NewGlobalRef(fileNameJString);
   708     ctx->hidden.androidio.inputStreamRef = NULL;
   709     ctx->hidden.androidio.readableByteChannelRef = NULL;
   710     ctx->hidden.androidio.readMethod = NULL;
   711     ctx->hidden.androidio.assetFileDescriptorRef = NULL;
   712 
   713     return Android_JNI_FileOpen(ctx);
   714 }
   715 
   716 extern "C" size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
   717         size_t size, size_t maxnum)
   718 {
   719     LocalReferenceHolder refs(__FUNCTION__);
   720 
   721     if (ctx->hidden.androidio.assetFileDescriptorRef) {
   722         size_t bytesMax = size * maxnum;
   723         if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && ctx->hidden.androidio.position + bytesMax > ctx->hidden.androidio.size) {
   724             bytesMax = ctx->hidden.androidio.size - ctx->hidden.androidio.position;
   725         }
   726         size_t result = read(ctx->hidden.androidio.fd, buffer, bytesMax );
   727         if (result > 0) {
   728             ctx->hidden.androidio.position += result;
   729             return result / size;
   730         }
   731         return 0;
   732     } else {
   733         jlong bytesRemaining = (jlong) (size * maxnum);
   734         jlong bytesMax = (jlong) (ctx->hidden.androidio.size -  ctx->hidden.androidio.position);
   735         int bytesRead = 0;
   736 
   737         /* Don't read more bytes than those that remain in the file, otherwise we get an exception */
   738         if (bytesRemaining >  bytesMax) bytesRemaining = bytesMax;
   739 
   740         JNIEnv *mEnv = Android_JNI_GetEnv();
   741         if (!refs.init(mEnv)) {
   742             return -1;
   743         }
   744 
   745         jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannelRef;
   746         jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
   747         jobject byteBuffer = mEnv->NewDirectByteBuffer(buffer, bytesRemaining);
   748 
   749         while (bytesRemaining > 0) {
   750             // result = readableByteChannel.read(...);
   751             int result = mEnv->CallIntMethod(readableByteChannel, readMethod, byteBuffer);
   752 
   753             if (Android_JNI_ExceptionOccurred()) {
   754                 return 0;
   755             }
   756 
   757             if (result < 0) {
   758                 break;
   759             }
   760 
   761             bytesRemaining -= result;
   762             bytesRead += result;
   763             ctx->hidden.androidio.position += result;
   764         }
   765         return bytesRead / size;
   766     }    
   767 }
   768 
   769 extern "C" size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
   770         size_t size, size_t num)
   771 {
   772     SDL_SetError("Cannot write to Android package filesystem");
   773     return 0;
   774 }
   775 
   776 static int Android_JNI_FileClose(SDL_RWops* ctx, bool release)
   777 {
   778     LocalReferenceHolder refs(__FUNCTION__);
   779     int result = 0;
   780     JNIEnv *mEnv = Android_JNI_GetEnv();
   781 
   782     if (!refs.init(mEnv)) {
   783         SDL_SetError("Failed to allocate enough JVM local references");
   784         return -1;
   785     }
   786 
   787     if (ctx) {
   788         if (release) {
   789             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   790         }
   791 
   792         if (ctx->hidden.androidio.assetFileDescriptorRef) {
   793             jobject inputStream = (jobject)ctx->hidden.androidio.assetFileDescriptorRef;
   794             jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   795                     "close", "()V");
   796             mEnv->CallVoidMethod(inputStream, mid);
   797             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.assetFileDescriptorRef);
   798             if (Android_JNI_ExceptionOccurred()) {
   799                 result = -1;
   800             }
   801         }
   802         else {
   803             jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
   804 
   805             // inputStream.close();
   806             jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   807                     "close", "()V");
   808             mEnv->CallVoidMethod(inputStream, mid);
   809             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   810             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
   811             if (Android_JNI_ExceptionOccurred()) {
   812                 result = -1;
   813             }
   814         }
   815 
   816         if (release) {
   817             SDL_FreeRW(ctx);
   818         }
   819     }
   820 
   821     return result;
   822 }
   823 
   824 
   825 extern "C" Sint64 Android_JNI_FileSize(SDL_RWops* ctx)
   826 {
   827     return ctx->hidden.androidio.size;
   828 }
   829 
   830 extern "C" Sint64 Android_JNI_FileSeek(SDL_RWops* ctx, Sint64 offset, int whence)
   831 {
   832     if (ctx->hidden.androidio.assetFileDescriptorRef) {
   833         switch (whence) {
   834             case RW_SEEK_SET:
   835                 if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
   836                 offset += ctx->hidden.androidio.offset;
   837                 break;
   838             case RW_SEEK_CUR:
   839                 offset += ctx->hidden.androidio.position;
   840                 if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
   841                 offset += ctx->hidden.androidio.offset;
   842                 break;
   843             case RW_SEEK_END:
   844                 offset = ctx->hidden.androidio.offset + ctx->hidden.androidio.size + offset;
   845                 break;
   846             default:
   847                 SDL_SetError("Unknown value for 'whence'");
   848                 return -1;
   849         }
   850         whence = SEEK_SET;
   851 
   852         off_t ret = lseek(ctx->hidden.androidio.fd, (off_t)offset, SEEK_SET);
   853         if (ret == -1) return -1;
   854         ctx->hidden.androidio.position = ret - ctx->hidden.androidio.offset;
   855     } else {
   856         Sint64 newPosition;
   857 
   858         switch (whence) {
   859             case RW_SEEK_SET:
   860                 newPosition = offset;
   861                 break;
   862             case RW_SEEK_CUR:
   863                 newPosition = ctx->hidden.androidio.position + offset;
   864                 break;
   865             case RW_SEEK_END:
   866                 newPosition = ctx->hidden.androidio.size + offset;
   867                 break;
   868             default:
   869                 SDL_SetError("Unknown value for 'whence'");
   870                 return -1;
   871         }
   872 
   873         /* Validate the new position */
   874         if (newPosition < 0) {
   875             SDL_Error(SDL_EFSEEK);
   876             return -1;
   877         }
   878         if (newPosition > ctx->hidden.androidio.size) {
   879             newPosition = ctx->hidden.androidio.size;
   880         }
   881 
   882         Sint64 movement = newPosition - ctx->hidden.androidio.position;
   883         if (movement > 0) {
   884             unsigned char buffer[4096];
   885 
   886             // The easy case where we're seeking forwards
   887             while (movement > 0) {
   888                 Sint64 amount = sizeof (buffer);
   889                 if (amount > movement) {
   890                     amount = movement;
   891                 }
   892                 size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount);
   893                 if (result <= 0) {
   894                     // Failed to read/skip the required amount, so fail
   895                     return -1;
   896                 }
   897 
   898                 movement -= result;
   899             }
   900 
   901         } else if (movement < 0) {
   902             // We can't seek backwards so we have to reopen the file and seek
   903             // forwards which obviously isn't very efficient
   904             Android_JNI_FileClose(ctx, false);
   905             Android_JNI_FileOpen(ctx);
   906             Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
   907         }
   908     }
   909 
   910     return ctx->hidden.androidio.position;
   911     
   912 }
   913 
   914 extern "C" int Android_JNI_FileClose(SDL_RWops* ctx)
   915 {
   916     return Android_JNI_FileClose(ctx, true);
   917 }
   918 
   919 // returns a new global reference which needs to be released later
   920 static jobject Android_JNI_GetSystemServiceObject(const char* name)
   921 {
   922     LocalReferenceHolder refs(__FUNCTION__);
   923     JNIEnv* env = Android_JNI_GetEnv();
   924     if (!refs.init(env)) {
   925         return NULL;
   926     }
   927 
   928     jstring service = env->NewStringUTF(name);
   929 
   930     jmethodID mid;
   931 
   932     mid = env->GetStaticMethodID(mActivityClass, "getContext", "()Landroid/content/Context;");
   933     jobject context = env->CallStaticObjectMethod(mActivityClass, mid);
   934 
   935     mid = env->GetMethodID(mActivityClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
   936     jobject manager = env->CallObjectMethod(context, mid, service);
   937 
   938     env->DeleteLocalRef(service);
   939 
   940     return manager ? env->NewGlobalRef(manager) : NULL;
   941 }
   942 
   943 #define SETUP_CLIPBOARD(error) \
   944     LocalReferenceHolder refs(__FUNCTION__); \
   945     JNIEnv* env = Android_JNI_GetEnv(); \
   946     if (!refs.init(env)) { \
   947         return error; \
   948     } \
   949     jobject clipboard = Android_JNI_GetSystemServiceObject("clipboard"); \
   950     if (!clipboard) { \
   951         return error; \
   952     }
   953 
   954 extern "C" int Android_JNI_SetClipboardText(const char* text)
   955 {
   956     SETUP_CLIPBOARD(-1)
   957 
   958     jmethodID mid = env->GetMethodID(env->GetObjectClass(clipboard), "setText", "(Ljava/lang/CharSequence;)V");
   959     jstring string = env->NewStringUTF(text);
   960     env->CallVoidMethod(clipboard, mid, string);
   961     env->DeleteGlobalRef(clipboard);
   962     env->DeleteLocalRef(string);
   963     return 0;
   964 }
   965 
   966 extern "C" char* Android_JNI_GetClipboardText()
   967 {
   968     SETUP_CLIPBOARD(SDL_strdup(""))
   969 
   970     jmethodID mid = env->GetMethodID(env->GetObjectClass(clipboard), "getText", "()Ljava/lang/CharSequence;");
   971     jobject sequence = env->CallObjectMethod(clipboard, mid);
   972     env->DeleteGlobalRef(clipboard);
   973     if (sequence) {
   974         mid = env->GetMethodID(env->GetObjectClass(sequence), "toString", "()Ljava/lang/String;");
   975         jstring string = reinterpret_cast<jstring>(env->CallObjectMethod(sequence, mid));
   976         const char* utf = env->GetStringUTFChars(string, 0);
   977         if (utf) {
   978             char* text = SDL_strdup(utf);
   979             env->ReleaseStringUTFChars(string, utf);
   980             return text;
   981         }
   982     }
   983     return SDL_strdup("");
   984 }
   985 
   986 extern "C" SDL_bool Android_JNI_HasClipboardText()
   987 {
   988     SETUP_CLIPBOARD(SDL_FALSE)
   989 
   990     jmethodID mid = env->GetMethodID(env->GetObjectClass(clipboard), "hasText", "()Z");
   991     jboolean has = env->CallBooleanMethod(clipboard, mid);
   992     env->DeleteGlobalRef(clipboard);
   993     return has ? SDL_TRUE : SDL_FALSE;
   994 }
   995 
   996 
   997 // returns 0 on success or -1 on error (others undefined then)
   998 // returns truthy or falsy value in plugged, charged and battery
   999 // returns the value in seconds and percent or -1 if not available
  1000 extern "C" int Android_JNI_GetPowerInfo(int* plugged, int* charged, int* battery, int* seconds, int* percent)
  1001 {
  1002     LocalReferenceHolder refs(__FUNCTION__);
  1003     JNIEnv* env = Android_JNI_GetEnv();
  1004     if (!refs.init(env)) {
  1005         return -1;
  1006     }
  1007 
  1008     jmethodID mid;
  1009 
  1010     mid = env->GetStaticMethodID(mActivityClass, "getContext", "()Landroid/content/Context;");
  1011     jobject context = env->CallStaticObjectMethod(mActivityClass, mid);
  1012 
  1013     jstring action = env->NewStringUTF("android.intent.action.BATTERY_CHANGED");
  1014 
  1015     jclass cls = env->FindClass("android/content/IntentFilter");
  1016 
  1017     mid = env->GetMethodID(cls, "<init>", "(Ljava/lang/String;)V");
  1018     jobject filter = env->NewObject(cls, mid, action);
  1019 
  1020     env->DeleteLocalRef(action);
  1021 
  1022     mid = env->GetMethodID(mActivityClass, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;");
  1023     jobject intent = env->CallObjectMethod(context, mid, NULL, filter);
  1024 
  1025     env->DeleteLocalRef(filter);
  1026 
  1027     cls = env->GetObjectClass(intent);
  1028 
  1029     jstring iname;
  1030     jmethodID imid = env->GetMethodID(cls, "getIntExtra", "(Ljava/lang/String;I)I");
  1031 
  1032 #define GET_INT_EXTRA(var, key) \
  1033     iname = env->NewStringUTF(key); \
  1034     int var = env->CallIntMethod(intent, imid, iname, -1); \
  1035     env->DeleteLocalRef(iname);
  1036 
  1037     jstring bname;
  1038     jmethodID bmid = env->GetMethodID(cls, "getBooleanExtra", "(Ljava/lang/String;Z)Z");
  1039 
  1040 #define GET_BOOL_EXTRA(var, key) \
  1041     bname = env->NewStringUTF(key); \
  1042     int var = env->CallBooleanMethod(intent, bmid, bname, JNI_FALSE); \
  1043     env->DeleteLocalRef(bname);
  1044 
  1045     if (plugged) {
  1046         GET_INT_EXTRA(plug, "plugged") // == BatteryManager.EXTRA_PLUGGED (API 5)
  1047         if (plug == -1) {
  1048             return -1;
  1049         }
  1050         // 1 == BatteryManager.BATTERY_PLUGGED_AC
  1051         // 2 == BatteryManager.BATTERY_PLUGGED_USB
  1052         *plugged = (0 < plug) ? 1 : 0;
  1053     }
  1054 
  1055     if (charged) {
  1056         GET_INT_EXTRA(status, "status") // == BatteryManager.EXTRA_STATUS (API 5)
  1057         if (status == -1) {
  1058             return -1;
  1059         }
  1060         // 5 == BatteryManager.BATTERY_STATUS_FULL
  1061         *charged = (status == 5) ? 1 : 0;
  1062     }
  1063 
  1064     if (battery) {
  1065         GET_BOOL_EXTRA(present, "present") // == BatteryManager.EXTRA_PRESENT (API 5)
  1066         *battery = present ? 1 : 0;
  1067     }
  1068 
  1069     if (seconds) {
  1070         *seconds = -1; // not possible
  1071     }
  1072 
  1073     if (percent) {
  1074         GET_INT_EXTRA(level, "level") // == BatteryManager.EXTRA_LEVEL (API 5)
  1075         GET_INT_EXTRA(scale, "scale") // == BatteryManager.EXTRA_SCALE (API 5)
  1076         if ((level == -1) || (scale == -1)) {
  1077             return -1;
  1078         }
  1079         *percent = level * 100 / scale;
  1080     }
  1081 
  1082     env->DeleteLocalRef(intent);
  1083 
  1084     return 0;
  1085 }
  1086 
  1087 // sends message to be handled on the UI event dispatch thread
  1088 extern "C" int Android_JNI_SendMessage(int command, int param)
  1089 {
  1090     JNIEnv *env = Android_JNI_GetEnv();
  1091     if (!env) {
  1092         return -1;
  1093     }
  1094     jmethodID mid = env->GetStaticMethodID(mActivityClass, "sendMessage", "(II)V");
  1095     if (!mid) {
  1096         return -1;
  1097     }
  1098     env->CallStaticVoidMethod(mActivityClass, mid, command, param);
  1099     return 0;
  1100 }
  1101 
  1102 extern "C" void Android_JNI_ShowTextInput(SDL_Rect *inputRect)
  1103 {
  1104     JNIEnv *env = Android_JNI_GetEnv();
  1105     if (!env) {
  1106         return;
  1107     }
  1108 
  1109     jmethodID mid = env->GetStaticMethodID(mActivityClass, "showTextInput", "(IIII)V");
  1110     if (!mid) {
  1111         return;
  1112     }
  1113     env->CallStaticVoidMethod( mActivityClass, mid,
  1114                                inputRect->x,
  1115                                inputRect->y,
  1116                                inputRect->w,
  1117                                inputRect->h );
  1118 }
  1119 
  1120 extern "C" void Android_JNI_HideTextInput()
  1121 {
  1122     // has to match Activity constant
  1123     const int COMMAND_TEXTEDIT_HIDE = 3;
  1124     Android_JNI_SendMessage(COMMAND_TEXTEDIT_HIDE, 0);
  1125 }
  1126 
  1127 //////////////////////////////////////////////////////////////////////////////
  1128 //
  1129 // Functions exposed to SDL applications in SDL_system.h
  1130 //
  1131 
  1132 extern "C" void *SDL_AndroidGetJNIEnv()
  1133 {
  1134     return Android_JNI_GetEnv();
  1135 }
  1136 
  1137 extern "C" void *SDL_AndroidGetActivity()
  1138 {
  1139     LocalReferenceHolder refs(__FUNCTION__);
  1140     jmethodID mid;
  1141 
  1142     JNIEnv *env = Android_JNI_GetEnv();
  1143     if (!refs.init(env)) {
  1144         return NULL;
  1145     }
  1146 
  1147     // return SDLActivity.getContext();
  1148     mid = env->GetStaticMethodID(mActivityClass,
  1149             "getContext","()Landroid/content/Context;");
  1150     return env->CallStaticObjectMethod(mActivityClass, mid);
  1151 }
  1152 
  1153 extern "C" const char * SDL_AndroidGetInternalStoragePath()
  1154 {
  1155     static char *s_AndroidInternalFilesPath = NULL;
  1156 
  1157     if (!s_AndroidInternalFilesPath) {
  1158         LocalReferenceHolder refs(__FUNCTION__);
  1159         jmethodID mid;
  1160         jobject context;
  1161         jobject fileObject;
  1162         jstring pathString;
  1163         const char *path;
  1164 
  1165         JNIEnv *env = Android_JNI_GetEnv();
  1166         if (!refs.init(env)) {
  1167             return NULL;
  1168         }
  1169 
  1170         // context = SDLActivity.getContext();
  1171         mid = env->GetStaticMethodID(mActivityClass,
  1172                 "getContext","()Landroid/content/Context;");
  1173         context = env->CallStaticObjectMethod(mActivityClass, mid);
  1174 
  1175         // fileObj = context.getFilesDir();
  1176         mid = env->GetMethodID(env->GetObjectClass(context),
  1177                 "getFilesDir", "()Ljava/io/File;");
  1178         fileObject = env->CallObjectMethod(context, mid);
  1179         if (!fileObject) {
  1180             SDL_SetError("Couldn't get internal directory");
  1181             return NULL;
  1182         }
  1183 
  1184         // path = fileObject.getAbsolutePath();
  1185         mid = env->GetMethodID(env->GetObjectClass(fileObject),
  1186                 "getAbsolutePath", "()Ljava/lang/String;");
  1187         pathString = (jstring)env->CallObjectMethod(fileObject, mid);
  1188 
  1189         path = env->GetStringUTFChars(pathString, NULL);
  1190         s_AndroidInternalFilesPath = SDL_strdup(path);
  1191         env->ReleaseStringUTFChars(pathString, path);
  1192     }
  1193     return s_AndroidInternalFilesPath;
  1194 }
  1195 
  1196 extern "C" int SDL_AndroidGetExternalStorageState()
  1197 {
  1198     LocalReferenceHolder refs(__FUNCTION__);
  1199     jmethodID mid;
  1200     jclass cls;
  1201     jstring stateString;
  1202     const char *state;
  1203     int stateFlags;
  1204 
  1205     JNIEnv *env = Android_JNI_GetEnv();
  1206     if (!refs.init(env)) {
  1207         return 0;
  1208     }
  1209 
  1210     cls = env->FindClass("android/os/Environment");
  1211     mid = env->GetStaticMethodID(cls,
  1212             "getExternalStorageState", "()Ljava/lang/String;");
  1213     stateString = (jstring)env->CallStaticObjectMethod(cls, mid);
  1214 
  1215     state = env->GetStringUTFChars(stateString, NULL);
  1216 
  1217     // Print an info message so people debugging know the storage state
  1218     __android_log_print(ANDROID_LOG_INFO, "SDL", "external storage state: %s", state);
  1219 
  1220     if (SDL_strcmp(state, "mounted") == 0) {
  1221         stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ |
  1222                      SDL_ANDROID_EXTERNAL_STORAGE_WRITE;
  1223     } else if (SDL_strcmp(state, "mounted_ro") == 0) {
  1224         stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ;
  1225     } else {
  1226         stateFlags = 0;
  1227     }
  1228     env->ReleaseStringUTFChars(stateString, state);
  1229 
  1230     return stateFlags;
  1231 }
  1232 
  1233 extern "C" const char * SDL_AndroidGetExternalStoragePath()
  1234 {
  1235     static char *s_AndroidExternalFilesPath = NULL;
  1236 
  1237     if (!s_AndroidExternalFilesPath) {
  1238         LocalReferenceHolder refs(__FUNCTION__);
  1239         jmethodID mid;
  1240         jobject context;
  1241         jobject fileObject;
  1242         jstring pathString;
  1243         const char *path;
  1244 
  1245         JNIEnv *env = Android_JNI_GetEnv();
  1246         if (!refs.init(env)) {
  1247             return NULL;
  1248         }
  1249 
  1250         // context = SDLActivity.getContext();
  1251         mid = env->GetStaticMethodID(mActivityClass,
  1252                 "getContext","()Landroid/content/Context;");
  1253         context = env->CallStaticObjectMethod(mActivityClass, mid);
  1254 
  1255         // fileObj = context.getExternalFilesDir();
  1256         mid = env->GetMethodID(env->GetObjectClass(context),
  1257                 "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;");
  1258         fileObject = env->CallObjectMethod(context, mid, NULL);
  1259         if (!fileObject) {
  1260             SDL_SetError("Couldn't get external directory");
  1261             return NULL;
  1262         }
  1263 
  1264         // path = fileObject.getAbsolutePath();
  1265         mid = env->GetMethodID(env->GetObjectClass(fileObject),
  1266                 "getAbsolutePath", "()Ljava/lang/String;");
  1267         pathString = (jstring)env->CallObjectMethod(fileObject, mid);
  1268 
  1269         path = env->GetStringUTFChars(pathString, NULL);
  1270         s_AndroidExternalFilesPath = SDL_strdup(path);
  1271         env->ReleaseStringUTFChars(pathString, path);
  1272     }
  1273     return s_AndroidExternalFilesPath;
  1274 }
  1275 
  1276 #endif /* __ANDROID__ */
  1277 
  1278 /* vi: set ts=4 sw=4 expandtab: */