src/core/android/SDL_android.cpp
author Sam Lantinga <slouken@libsdl.org>
Fri, 15 Feb 2013 08:47:44 -0800
changeset 6885 700f1b25f77f
parent 6864 97187387ad79
child 7018 9cef1005df5f
permissions -rw-r--r--
Happy New Year!
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2013 Sam Lantinga <slouken@libsdl.org>
     4 
     5   This software is provided 'as-is', without any express or implied
     6   warranty.  In no event will the authors be held liable for any damages
     7   arising from the use of this software.
     8 
     9   Permission is granted to anyone to use this software for any purpose,
    10   including commercial applications, and to alter it and redistribute it
    11   freely, subject to the following restrictions:
    12 
    13   1. The origin of this software must not be misrepresented; you must not
    14      claim that you wrote the original software. If you use this software
    15      in a product, an acknowledgment in the product documentation would be
    16      appreciated but is not required.
    17   2. Altered source versions must be plainly marked as such, and must not be
    18      misrepresented as being the original software.
    19   3. This notice may not be removed or altered from any source distribution.
    20 */
    21 #include "SDL_config.h"
    22 #include "SDL_stdinc.h"
    23 #include "SDL_assert.h"
    24 #include "SDL_log.h"
    25 
    26 #ifdef __ANDROID__
    27 
    28 #include "SDL_system.h"
    29 #include "SDL_android.h"
    30 #include <EGL/egl.h>
    31 
    32 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     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device");
   434     audioBuffer16Bit = is16Bit;
   435     audioBufferStereo = channelCount > 1;
   436 
   437     env->CallStaticVoidMethod(mActivityClass, midAudioInit, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames);
   438 
   439     /* Allocating the audio buffer from the Java side and passing it as the return value for audioInit no longer works on
   440      * Android >= 4.2 due to a "stale global reference" error. So now we allocate this buffer directly from this side. */
   441     
   442     if (is16Bit) {
   443         jshortArray audioBufferLocal = env->NewShortArray(desiredBufferFrames * (audioBufferStereo ? 2 : 1));
   444         if (audioBufferLocal) {
   445             audioBuffer = env->NewGlobalRef(audioBufferLocal);
   446             env->DeleteLocalRef(audioBufferLocal);
   447         }
   448     }
   449     else {
   450         jbyteArray audioBufferLocal = env->NewByteArray(desiredBufferFrames * (audioBufferStereo ? 2 : 1));
   451         if (audioBufferLocal) {
   452             audioBuffer = env->NewGlobalRef(audioBufferLocal);
   453             env->DeleteLocalRef(audioBufferLocal);
   454         }
   455     }
   456 
   457     if (audioBuffer == NULL) {
   458         __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: could not allocate an audio buffer!");
   459         return 0;
   460     }
   461 
   462     jboolean isCopy = JNI_FALSE;
   463     if (audioBuffer16Bit) {
   464         audioBufferPinned = env->GetShortArrayElements((jshortArray)audioBuffer, &isCopy);
   465         audioBufferFrames = env->GetArrayLength((jshortArray)audioBuffer);
   466     } else {
   467         audioBufferPinned = env->GetByteArrayElements((jbyteArray)audioBuffer, &isCopy);
   468         audioBufferFrames = env->GetArrayLength((jbyteArray)audioBuffer);
   469     }
   470     if (audioBufferStereo) {
   471         audioBufferFrames /= 2;
   472     }
   473 
   474     return audioBufferFrames;
   475 }
   476 
   477 extern "C" void * Android_JNI_GetAudioBuffer()
   478 {
   479     return audioBufferPinned;
   480 }
   481 
   482 extern "C" void Android_JNI_WriteAudioBuffer()
   483 {
   484     JNIEnv *mAudioEnv = Android_JNI_GetEnv();
   485 
   486     if (audioBuffer16Bit) {
   487         mAudioEnv->ReleaseShortArrayElements((jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
   488         mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
   489     } else {
   490         mAudioEnv->ReleaseByteArrayElements((jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT);
   491         mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer);
   492     }
   493 
   494     /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
   495 }
   496 
   497 extern "C" void Android_JNI_CloseAudioDevice()
   498 {
   499     int status;
   500     JNIEnv *env = Android_JNI_GetEnv();
   501 
   502     env->CallStaticVoidMethod(mActivityClass, midAudioQuit); 
   503 
   504     if (audioBuffer) {
   505         env->DeleteGlobalRef(audioBuffer);
   506         audioBuffer = NULL;
   507         audioBufferPinned = NULL;
   508     }
   509 }
   510 
   511 // Test for an exception and call SDL_SetError with its detail if one occurs
   512 static bool Android_JNI_ExceptionOccurred()
   513 {
   514     SDL_assert(LocalReferenceHolder::IsActive());
   515     JNIEnv *mEnv = Android_JNI_GetEnv();
   516 
   517     jthrowable exception = mEnv->ExceptionOccurred();
   518     if (exception != NULL) {
   519         jmethodID mid;
   520 
   521         // Until this happens most JNI operations have undefined behaviour
   522         mEnv->ExceptionClear();
   523 
   524         jclass exceptionClass = mEnv->GetObjectClass(exception);
   525         jclass classClass = mEnv->FindClass("java/lang/Class");
   526 
   527         mid = mEnv->GetMethodID(classClass, "getName", "()Ljava/lang/String;");
   528         jstring exceptionName = (jstring)mEnv->CallObjectMethod(exceptionClass, mid);
   529         const char* exceptionNameUTF8 = mEnv->GetStringUTFChars(exceptionName, 0);
   530 
   531         mid = mEnv->GetMethodID(exceptionClass, "getMessage", "()Ljava/lang/String;");
   532         jstring exceptionMessage = (jstring)mEnv->CallObjectMethod(exception, mid);
   533 
   534         if (exceptionMessage != NULL) {
   535             const char* exceptionMessageUTF8 = mEnv->GetStringUTFChars(
   536                     exceptionMessage, 0);
   537             SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
   538             mEnv->ReleaseStringUTFChars(exceptionMessage, exceptionMessageUTF8);
   539         } else {
   540             SDL_SetError("%s", exceptionNameUTF8);
   541         }
   542 
   543         mEnv->ReleaseStringUTFChars(exceptionName, exceptionNameUTF8);
   544 
   545         return true;
   546     }
   547 
   548     return false;
   549 }
   550 
   551 static int Android_JNI_FileOpen(SDL_RWops* ctx)
   552 {
   553     LocalReferenceHolder refs(__FUNCTION__);
   554     int result = 0;
   555 
   556     jmethodID mid;
   557     jobject context;
   558     jobject assetManager;
   559     jobject inputStream;
   560     jclass channels;
   561     jobject readableByteChannel;
   562     jstring fileNameJString;
   563     jobject fd;
   564     jclass fdCls;
   565     jfieldID descriptor;
   566 
   567     JNIEnv *mEnv = Android_JNI_GetEnv();
   568     if (!refs.init(mEnv)) {
   569         goto failure;
   570     }
   571 
   572     fileNameJString = (jstring)ctx->hidden.androidio.fileNameRef;
   573     ctx->hidden.androidio.position = 0;
   574 
   575     // context = SDLActivity.getContext();
   576     mid = mEnv->GetStaticMethodID(mActivityClass,
   577             "getContext","()Landroid/content/Context;");
   578     context = mEnv->CallStaticObjectMethod(mActivityClass, mid);
   579     
   580 
   581     // assetManager = context.getAssets();
   582     mid = mEnv->GetMethodID(mEnv->GetObjectClass(context),
   583             "getAssets", "()Landroid/content/res/AssetManager;");
   584     assetManager = mEnv->CallObjectMethod(context, mid);
   585 
   586     /* First let's try opening the file to obtain an AssetFileDescriptor.
   587     * This method reads the files directly from the APKs using standard *nix calls
   588     */
   589     mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager), "openFd", "(Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;");
   590     inputStream = mEnv->CallObjectMethod(assetManager, mid, fileNameJString);
   591     if (Android_JNI_ExceptionOccurred()) {
   592         goto fallback;
   593     }
   594 
   595     mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream), "getStartOffset", "()J");
   596     ctx->hidden.androidio.offset = mEnv->CallLongMethod(inputStream, mid);
   597     if (Android_JNI_ExceptionOccurred()) {
   598         goto fallback;
   599     }
   600 
   601     mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream), "getDeclaredLength", "()J");
   602     ctx->hidden.androidio.size = mEnv->CallLongMethod(inputStream, mid);
   603     if (Android_JNI_ExceptionOccurred()) {
   604         goto fallback;
   605     }
   606 
   607     mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream), "getFileDescriptor", "()Ljava/io/FileDescriptor;");
   608     fd = mEnv->CallObjectMethod(inputStream, mid);
   609     fdCls = mEnv->GetObjectClass(fd);
   610     descriptor = mEnv->GetFieldID(fdCls, "descriptor", "I");
   611     ctx->hidden.androidio.fd = mEnv->GetIntField(fd, descriptor);
   612     ctx->hidden.androidio.assetFileDescriptorRef = mEnv->NewGlobalRef(inputStream);
   613 
   614     // Seek to the correct offset in the file.
   615     lseek(ctx->hidden.androidio.fd, (off_t)ctx->hidden.androidio.offset, SEEK_SET);
   616 
   617     if (false) {
   618 fallback:
   619         __android_log_print(ANDROID_LOG_DEBUG, "SDL", "Falling back to legacy InputStream method for opening file");
   620         /* Try the old method using InputStream */
   621         ctx->hidden.androidio.assetFileDescriptorRef = NULL;
   622 
   623         // inputStream = assetManager.open(<filename>);
   624         mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager),
   625                 "open", "(Ljava/lang/String;I)Ljava/io/InputStream;");
   626         inputStream = mEnv->CallObjectMethod(assetManager, mid, fileNameJString, 1 /*ACCESS_RANDOM*/);
   627         if (Android_JNI_ExceptionOccurred()) {
   628             goto failure;
   629         }
   630 
   631         ctx->hidden.androidio.inputStreamRef = mEnv->NewGlobalRef(inputStream);
   632 
   633         // Despite all the visible documentation on [Asset]InputStream claiming
   634         // that the .available() method is not guaranteed to return the entire file
   635         // size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
   636         // android/apis/content/ReadAsset.java imply that Android's
   637         // AssetInputStream.available() /will/ always return the total file size
   638 
   639         // size = inputStream.available();
   640         mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   641                 "available", "()I");
   642         ctx->hidden.androidio.size = (long)mEnv->CallIntMethod(inputStream, mid);
   643         if (Android_JNI_ExceptionOccurred()) {
   644             goto failure;
   645         }
   646 
   647         // readableByteChannel = Channels.newChannel(inputStream);
   648         channels = mEnv->FindClass("java/nio/channels/Channels");
   649         mid = mEnv->GetStaticMethodID(channels,
   650                 "newChannel",
   651                 "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
   652         readableByteChannel = mEnv->CallStaticObjectMethod(
   653                 channels, mid, inputStream);
   654         if (Android_JNI_ExceptionOccurred()) {
   655             goto failure;
   656         }
   657 
   658         ctx->hidden.androidio.readableByteChannelRef =
   659             mEnv->NewGlobalRef(readableByteChannel);
   660 
   661         // Store .read id for reading purposes
   662         mid = mEnv->GetMethodID(mEnv->GetObjectClass(readableByteChannel),
   663                 "read", "(Ljava/nio/ByteBuffer;)I");
   664         ctx->hidden.androidio.readMethod = mid;
   665     }
   666 
   667     if (false) {
   668 failure:
   669         result = -1;
   670 
   671         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   672 
   673         if(ctx->hidden.androidio.inputStreamRef != NULL) {
   674             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   675         }
   676 
   677         if(ctx->hidden.androidio.readableByteChannelRef != NULL) {
   678             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
   679         }
   680 
   681         if(ctx->hidden.androidio.assetFileDescriptorRef != NULL) {
   682             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.assetFileDescriptorRef);
   683         }
   684 
   685     }
   686 
   687     return result;
   688 }
   689 
   690 extern "C" int Android_JNI_FileOpen(SDL_RWops* ctx,
   691         const char* fileName, const char*)
   692 {
   693     LocalReferenceHolder refs(__FUNCTION__);
   694     JNIEnv *mEnv = Android_JNI_GetEnv();
   695 
   696     if (!refs.init(mEnv)) {
   697         return -1;
   698     }
   699 
   700     if (!ctx) {
   701         return -1;
   702     }
   703 
   704     jstring fileNameJString = mEnv->NewStringUTF(fileName);
   705     ctx->hidden.androidio.fileNameRef = mEnv->NewGlobalRef(fileNameJString);
   706     ctx->hidden.androidio.inputStreamRef = NULL;
   707     ctx->hidden.androidio.readableByteChannelRef = NULL;
   708     ctx->hidden.androidio.readMethod = NULL;
   709     ctx->hidden.androidio.assetFileDescriptorRef = NULL;
   710 
   711     return Android_JNI_FileOpen(ctx);
   712 }
   713 
   714 extern "C" size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
   715         size_t size, size_t maxnum)
   716 {
   717     LocalReferenceHolder refs(__FUNCTION__);
   718 
   719     if (ctx->hidden.androidio.assetFileDescriptorRef) {
   720         size_t bytesMax = size * maxnum;
   721         if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && ctx->hidden.androidio.position + bytesMax > ctx->hidden.androidio.size) {
   722             bytesMax = ctx->hidden.androidio.size - ctx->hidden.androidio.position;
   723         }
   724         size_t result = read(ctx->hidden.androidio.fd, buffer, bytesMax );
   725         if (result > 0) {
   726             ctx->hidden.androidio.position += result;
   727             return result / size;
   728         }
   729         return 0;
   730     } else {
   731         jlong bytesRemaining = (jlong) (size * maxnum);
   732         jlong bytesMax = (jlong) (ctx->hidden.androidio.size -  ctx->hidden.androidio.position);
   733         int bytesRead = 0;
   734 
   735         /* Don't read more bytes than those that remain in the file, otherwise we get an exception */
   736         if (bytesRemaining >  bytesMax) bytesRemaining = bytesMax;
   737 
   738         JNIEnv *mEnv = Android_JNI_GetEnv();
   739         if (!refs.init(mEnv)) {
   740             return -1;
   741         }
   742 
   743         jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannelRef;
   744         jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
   745         jobject byteBuffer = mEnv->NewDirectByteBuffer(buffer, bytesRemaining);
   746 
   747         while (bytesRemaining > 0) {
   748             // result = readableByteChannel.read(...);
   749             int result = mEnv->CallIntMethod(readableByteChannel, readMethod, byteBuffer);
   750 
   751             if (Android_JNI_ExceptionOccurred()) {
   752                 return 0;
   753             }
   754 
   755             if (result < 0) {
   756                 break;
   757             }
   758 
   759             bytesRemaining -= result;
   760             bytesRead += result;
   761             ctx->hidden.androidio.position += result;
   762         }
   763         return bytesRead / size;
   764     }    
   765 }
   766 
   767 extern "C" size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
   768         size_t size, size_t num)
   769 {
   770     SDL_SetError("Cannot write to Android package filesystem");
   771     return 0;
   772 }
   773 
   774 static int Android_JNI_FileClose(SDL_RWops* ctx, bool release)
   775 {
   776     LocalReferenceHolder refs(__FUNCTION__);
   777     int result = 0;
   778     JNIEnv *mEnv = Android_JNI_GetEnv();
   779 
   780     if (!refs.init(mEnv)) {
   781         SDL_SetError("Failed to allocate enough JVM local references");
   782         return -1;
   783     }
   784 
   785     if (ctx) {
   786         if (release) {
   787             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   788         }
   789 
   790         if (ctx->hidden.androidio.assetFileDescriptorRef) {
   791             jobject inputStream = (jobject)ctx->hidden.androidio.assetFileDescriptorRef;
   792             jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   793                     "close", "()V");
   794             mEnv->CallVoidMethod(inputStream, mid);
   795             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.assetFileDescriptorRef);
   796             if (Android_JNI_ExceptionOccurred()) {
   797                 result = -1;
   798             }
   799         }
   800         else {
   801             jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
   802 
   803             // inputStream.close();
   804             jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   805                     "close", "()V");
   806             mEnv->CallVoidMethod(inputStream, mid);
   807             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   808             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
   809             if (Android_JNI_ExceptionOccurred()) {
   810                 result = -1;
   811             }
   812         }
   813 
   814         if (release) {
   815             SDL_FreeRW(ctx);
   816         }
   817     }
   818 
   819     return result;
   820 }
   821 
   822 
   823 extern "C" Sint64 Android_JNI_FileSize(SDL_RWops* ctx)
   824 {
   825     return ctx->hidden.androidio.size;
   826 }
   827 
   828 extern "C" Sint64 Android_JNI_FileSeek(SDL_RWops* ctx, Sint64 offset, int whence)
   829 {
   830     if (ctx->hidden.androidio.assetFileDescriptorRef) {
   831         switch (whence) {
   832             case RW_SEEK_SET:
   833                 if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
   834                 offset += ctx->hidden.androidio.offset;
   835                 break;
   836             case RW_SEEK_CUR:
   837                 offset += ctx->hidden.androidio.position;
   838                 if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
   839                 offset += ctx->hidden.androidio.offset;
   840                 break;
   841             case RW_SEEK_END:
   842                 offset = ctx->hidden.androidio.offset + ctx->hidden.androidio.size + offset;
   843                 break;
   844             default:
   845                 SDL_SetError("Unknown value for 'whence'");
   846                 return -1;
   847         }
   848         whence = SEEK_SET;
   849 
   850         off_t ret = lseek(ctx->hidden.androidio.fd, (off_t)offset, SEEK_SET);
   851         if (ret == -1) return -1;
   852         ctx->hidden.androidio.position = ret - ctx->hidden.androidio.offset;
   853     } else {
   854         Sint64 newPosition;
   855 
   856         switch (whence) {
   857             case RW_SEEK_SET:
   858                 newPosition = offset;
   859                 break;
   860             case RW_SEEK_CUR:
   861                 newPosition = ctx->hidden.androidio.position + offset;
   862                 break;
   863             case RW_SEEK_END:
   864                 newPosition = ctx->hidden.androidio.size + offset;
   865                 break;
   866             default:
   867                 SDL_SetError("Unknown value for 'whence'");
   868                 return -1;
   869         }
   870 
   871         /* Validate the new position */
   872         if (newPosition < 0) {
   873             SDL_Error(SDL_EFSEEK);
   874             return -1;
   875         }
   876         if (newPosition > ctx->hidden.androidio.size) {
   877             newPosition = ctx->hidden.androidio.size;
   878         }
   879 
   880         Sint64 movement = newPosition - ctx->hidden.androidio.position;
   881         if (movement > 0) {
   882             unsigned char buffer[4096];
   883 
   884             // The easy case where we're seeking forwards
   885             while (movement > 0) {
   886                 Sint64 amount = sizeof (buffer);
   887                 if (amount > movement) {
   888                     amount = movement;
   889                 }
   890                 size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount);
   891                 if (result <= 0) {
   892                     // Failed to read/skip the required amount, so fail
   893                     return -1;
   894                 }
   895 
   896                 movement -= result;
   897             }
   898 
   899         } else if (movement < 0) {
   900             // We can't seek backwards so we have to reopen the file and seek
   901             // forwards which obviously isn't very efficient
   902             Android_JNI_FileClose(ctx, false);
   903             Android_JNI_FileOpen(ctx);
   904             Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
   905         }
   906     }
   907 
   908     return ctx->hidden.androidio.position;
   909     
   910 }
   911 
   912 extern "C" int Android_JNI_FileClose(SDL_RWops* ctx)
   913 {
   914     return Android_JNI_FileClose(ctx, true);
   915 }
   916 
   917 // returns a new global reference which needs to be released later
   918 static jobject Android_JNI_GetSystemServiceObject(const char* name)
   919 {
   920     LocalReferenceHolder refs(__FUNCTION__);
   921     JNIEnv* env = Android_JNI_GetEnv();
   922     if (!refs.init(env)) {
   923         return NULL;
   924     }
   925 
   926     jstring service = env->NewStringUTF(name);
   927 
   928     jmethodID mid;
   929 
   930     mid = env->GetStaticMethodID(mActivityClass, "getContext", "()Landroid/content/Context;");
   931     jobject context = env->CallStaticObjectMethod(mActivityClass, mid);
   932 
   933     mid = env->GetMethodID(mActivityClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
   934     jobject manager = env->CallObjectMethod(context, mid, service);
   935 
   936     env->DeleteLocalRef(service);
   937 
   938     return manager ? env->NewGlobalRef(manager) : NULL;
   939 }
   940 
   941 #define SETUP_CLIPBOARD(error) \
   942     LocalReferenceHolder refs(__FUNCTION__); \
   943     JNIEnv* env = Android_JNI_GetEnv(); \
   944     if (!refs.init(env)) { \
   945         return error; \
   946     } \
   947     jobject clipboard = Android_JNI_GetSystemServiceObject("clipboard"); \
   948     if (!clipboard) { \
   949         return error; \
   950     }
   951 
   952 extern "C" int Android_JNI_SetClipboardText(const char* text)
   953 {
   954     SETUP_CLIPBOARD(-1)
   955 
   956     jmethodID mid = env->GetMethodID(env->GetObjectClass(clipboard), "setText", "(Ljava/lang/CharSequence;)V");
   957     jstring string = env->NewStringUTF(text);
   958     env->CallVoidMethod(clipboard, mid, string);
   959     env->DeleteGlobalRef(clipboard);
   960     env->DeleteLocalRef(string);
   961     return 0;
   962 }
   963 
   964 extern "C" char* Android_JNI_GetClipboardText()
   965 {
   966     SETUP_CLIPBOARD(SDL_strdup(""))
   967 
   968     jmethodID mid = env->GetMethodID(env->GetObjectClass(clipboard), "getText", "()Ljava/lang/CharSequence;");
   969     jobject sequence = env->CallObjectMethod(clipboard, mid);
   970     env->DeleteGlobalRef(clipboard);
   971     if (sequence) {
   972         mid = env->GetMethodID(env->GetObjectClass(sequence), "toString", "()Ljava/lang/String;");
   973         jstring string = reinterpret_cast<jstring>(env->CallObjectMethod(sequence, mid));
   974         const char* utf = env->GetStringUTFChars(string, 0);
   975         if (utf) {
   976             char* text = SDL_strdup(utf);
   977             env->ReleaseStringUTFChars(string, utf);
   978             return text;
   979         }
   980     }
   981     return SDL_strdup("");
   982 }
   983 
   984 extern "C" SDL_bool Android_JNI_HasClipboardText()
   985 {
   986     SETUP_CLIPBOARD(SDL_FALSE)
   987 
   988     jmethodID mid = env->GetMethodID(env->GetObjectClass(clipboard), "hasText", "()Z");
   989     jboolean has = env->CallBooleanMethod(clipboard, mid);
   990     env->DeleteGlobalRef(clipboard);
   991     return has ? SDL_TRUE : SDL_FALSE;
   992 }
   993 
   994 
   995 // returns 0 on success or -1 on error (others undefined then)
   996 // returns truthy or falsy value in plugged, charged and battery
   997 // returns the value in seconds and percent or -1 if not available
   998 extern "C" int Android_JNI_GetPowerInfo(int* plugged, int* charged, int* battery, int* seconds, int* percent)
   999 {
  1000     LocalReferenceHolder refs(__FUNCTION__);
  1001     JNIEnv* env = Android_JNI_GetEnv();
  1002     if (!refs.init(env)) {
  1003         return -1;
  1004     }
  1005 
  1006     jmethodID mid;
  1007 
  1008     mid = env->GetStaticMethodID(mActivityClass, "getContext", "()Landroid/content/Context;");
  1009     jobject context = env->CallStaticObjectMethod(mActivityClass, mid);
  1010 
  1011     jstring action = env->NewStringUTF("android.intent.action.BATTERY_CHANGED");
  1012 
  1013     jclass cls = env->FindClass("android/content/IntentFilter");
  1014 
  1015     mid = env->GetMethodID(cls, "<init>", "(Ljava/lang/String;)V");
  1016     jobject filter = env->NewObject(cls, mid, action);
  1017 
  1018     env->DeleteLocalRef(action);
  1019 
  1020     mid = env->GetMethodID(mActivityClass, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;");
  1021     jobject intent = env->CallObjectMethod(context, mid, NULL, filter);
  1022 
  1023     env->DeleteLocalRef(filter);
  1024 
  1025     cls = env->GetObjectClass(intent);
  1026 
  1027     jstring iname;
  1028     jmethodID imid = env->GetMethodID(cls, "getIntExtra", "(Ljava/lang/String;I)I");
  1029 
  1030 #define GET_INT_EXTRA(var, key) \
  1031     iname = env->NewStringUTF(key); \
  1032     int var = env->CallIntMethod(intent, imid, iname, -1); \
  1033     env->DeleteLocalRef(iname);
  1034 
  1035     jstring bname;
  1036     jmethodID bmid = env->GetMethodID(cls, "getBooleanExtra", "(Ljava/lang/String;Z)Z");
  1037 
  1038 #define GET_BOOL_EXTRA(var, key) \
  1039     bname = env->NewStringUTF(key); \
  1040     int var = env->CallBooleanMethod(intent, bmid, bname, JNI_FALSE); \
  1041     env->DeleteLocalRef(bname);
  1042 
  1043     if (plugged) {
  1044         GET_INT_EXTRA(plug, "plugged") // == BatteryManager.EXTRA_PLUGGED (API 5)
  1045         if (plug == -1) {
  1046             return -1;
  1047         }
  1048         // 1 == BatteryManager.BATTERY_PLUGGED_AC
  1049         // 2 == BatteryManager.BATTERY_PLUGGED_USB
  1050         *plugged = (0 < plug) ? 1 : 0;
  1051     }
  1052 
  1053     if (charged) {
  1054         GET_INT_EXTRA(status, "status") // == BatteryManager.EXTRA_STATUS (API 5)
  1055         if (status == -1) {
  1056             return -1;
  1057         }
  1058         // 5 == BatteryManager.BATTERY_STATUS_FULL
  1059         *charged = (status == 5) ? 1 : 0;
  1060     }
  1061 
  1062     if (battery) {
  1063         GET_BOOL_EXTRA(present, "present") // == BatteryManager.EXTRA_PRESENT (API 5)
  1064         *battery = present ? 1 : 0;
  1065     }
  1066 
  1067     if (seconds) {
  1068         *seconds = -1; // not possible
  1069     }
  1070 
  1071     if (percent) {
  1072         GET_INT_EXTRA(level, "level") // == BatteryManager.EXTRA_LEVEL (API 5)
  1073         GET_INT_EXTRA(scale, "scale") // == BatteryManager.EXTRA_SCALE (API 5)
  1074         if ((level == -1) || (scale == -1)) {
  1075             return -1;
  1076         }
  1077         *percent = level * 100 / scale;
  1078     }
  1079 
  1080     env->DeleteLocalRef(intent);
  1081 
  1082     return 0;
  1083 }
  1084 
  1085 // sends message to be handled on the UI event dispatch thread
  1086 extern "C" int Android_JNI_SendMessage(int command, int param)
  1087 {
  1088     JNIEnv *env = Android_JNI_GetEnv();
  1089     if (!env) {
  1090         return -1;
  1091     }
  1092     jmethodID mid = env->GetStaticMethodID(mActivityClass, "sendMessage", "(II)V");
  1093     if (!mid) {
  1094         return -1;
  1095     }
  1096     env->CallStaticVoidMethod(mActivityClass, mid, command, param);
  1097     return 0;
  1098 }
  1099 
  1100 extern "C" void Android_JNI_ShowTextInput(SDL_Rect *inputRect)
  1101 {
  1102     JNIEnv *env = Android_JNI_GetEnv();
  1103     if (!env) {
  1104         return;
  1105     }
  1106 
  1107     jmethodID mid = env->GetStaticMethodID(mActivityClass, "showTextInput", "(IIII)V");
  1108     if (!mid) {
  1109         return;
  1110     }
  1111     env->CallStaticVoidMethod( mActivityClass, mid,
  1112                                inputRect->x,
  1113                                inputRect->y,
  1114                                inputRect->w,
  1115                                inputRect->h );
  1116 }
  1117 
  1118 extern "C" void Android_JNI_HideTextInput()
  1119 {
  1120     // has to match Activity constant
  1121     const int COMMAND_TEXTEDIT_HIDE = 3;
  1122     Android_JNI_SendMessage(COMMAND_TEXTEDIT_HIDE, 0);
  1123 }
  1124 
  1125 //////////////////////////////////////////////////////////////////////////////
  1126 //
  1127 // Functions exposed to SDL applications in SDL_system.h
  1128 //
  1129 
  1130 extern "C" void *SDL_AndroidGetJNIEnv()
  1131 {
  1132     return Android_JNI_GetEnv();
  1133 }
  1134 
  1135 extern "C" void *SDL_AndroidGetActivity()
  1136 {
  1137     LocalReferenceHolder refs(__FUNCTION__);
  1138     jmethodID mid;
  1139 
  1140     JNIEnv *env = Android_JNI_GetEnv();
  1141     if (!refs.init(env)) {
  1142         return NULL;
  1143     }
  1144 
  1145     // return SDLActivity.getContext();
  1146     mid = env->GetStaticMethodID(mActivityClass,
  1147             "getContext","()Landroid/content/Context;");
  1148     return env->CallStaticObjectMethod(mActivityClass, mid);
  1149 }
  1150 
  1151 extern "C" const char * SDL_AndroidGetInternalStoragePath()
  1152 {
  1153     static char *s_AndroidInternalFilesPath = NULL;
  1154 
  1155     if (!s_AndroidInternalFilesPath) {
  1156         LocalReferenceHolder refs(__FUNCTION__);
  1157         jmethodID mid;
  1158         jobject context;
  1159         jobject fileObject;
  1160         jstring pathString;
  1161         const char *path;
  1162 
  1163         JNIEnv *env = Android_JNI_GetEnv();
  1164         if (!refs.init(env)) {
  1165             return NULL;
  1166         }
  1167 
  1168         // context = SDLActivity.getContext();
  1169         mid = env->GetStaticMethodID(mActivityClass,
  1170                 "getContext","()Landroid/content/Context;");
  1171         context = env->CallStaticObjectMethod(mActivityClass, mid);
  1172 
  1173         // fileObj = context.getFilesDir();
  1174         mid = env->GetMethodID(env->GetObjectClass(context),
  1175                 "getFilesDir", "()Ljava/io/File;");
  1176         fileObject = env->CallObjectMethod(context, mid);
  1177         if (!fileObject) {
  1178             SDL_SetError("Couldn't get internal directory");
  1179             return NULL;
  1180         }
  1181 
  1182         // path = fileObject.getAbsolutePath();
  1183         mid = env->GetMethodID(env->GetObjectClass(fileObject),
  1184                 "getAbsolutePath", "()Ljava/lang/String;");
  1185         pathString = (jstring)env->CallObjectMethod(fileObject, mid);
  1186 
  1187         path = env->GetStringUTFChars(pathString, NULL);
  1188         s_AndroidInternalFilesPath = SDL_strdup(path);
  1189         env->ReleaseStringUTFChars(pathString, path);
  1190     }
  1191     return s_AndroidInternalFilesPath;
  1192 }
  1193 
  1194 extern "C" int SDL_AndroidGetExternalStorageState()
  1195 {
  1196     LocalReferenceHolder refs(__FUNCTION__);
  1197     jmethodID mid;
  1198     jclass cls;
  1199     jstring stateString;
  1200     const char *state;
  1201     int stateFlags;
  1202 
  1203     JNIEnv *env = Android_JNI_GetEnv();
  1204     if (!refs.init(env)) {
  1205         return 0;
  1206     }
  1207 
  1208     cls = env->FindClass("android/os/Environment");
  1209     mid = env->GetStaticMethodID(cls,
  1210             "getExternalStorageState", "()Ljava/lang/String;");
  1211     stateString = (jstring)env->CallStaticObjectMethod(cls, mid);
  1212 
  1213     state = env->GetStringUTFChars(stateString, NULL);
  1214 
  1215     // Print an info message so people debugging know the storage state
  1216     __android_log_print(ANDROID_LOG_INFO, "SDL", "external storage state: %s", state);
  1217 
  1218     if (SDL_strcmp(state, "mounted") == 0) {
  1219         stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ |
  1220                      SDL_ANDROID_EXTERNAL_STORAGE_WRITE;
  1221     } else if (SDL_strcmp(state, "mounted_ro") == 0) {
  1222         stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ;
  1223     } else {
  1224         stateFlags = 0;
  1225     }
  1226     env->ReleaseStringUTFChars(stateString, state);
  1227 
  1228     return stateFlags;
  1229 }
  1230 
  1231 extern "C" const char * SDL_AndroidGetExternalStoragePath()
  1232 {
  1233     static char *s_AndroidExternalFilesPath = NULL;
  1234 
  1235     if (!s_AndroidExternalFilesPath) {
  1236         LocalReferenceHolder refs(__FUNCTION__);
  1237         jmethodID mid;
  1238         jobject context;
  1239         jobject fileObject;
  1240         jstring pathString;
  1241         const char *path;
  1242 
  1243         JNIEnv *env = Android_JNI_GetEnv();
  1244         if (!refs.init(env)) {
  1245             return NULL;
  1246         }
  1247 
  1248         // context = SDLActivity.getContext();
  1249         mid = env->GetStaticMethodID(mActivityClass,
  1250                 "getContext","()Landroid/content/Context;");
  1251         context = env->CallStaticObjectMethod(mActivityClass, mid);
  1252 
  1253         // fileObj = context.getExternalFilesDir();
  1254         mid = env->GetMethodID(env->GetObjectClass(context),
  1255                 "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;");
  1256         fileObject = env->CallObjectMethod(context, mid, NULL);
  1257         if (!fileObject) {
  1258             SDL_SetError("Couldn't get external directory");
  1259             return NULL;
  1260         }
  1261 
  1262         // path = fileObject.getAbsolutePath();
  1263         mid = env->GetMethodID(env->GetObjectClass(fileObject),
  1264                 "getAbsolutePath", "()Ljava/lang/String;");
  1265         pathString = (jstring)env->CallObjectMethod(fileObject, mid);
  1266 
  1267         path = env->GetStringUTFChars(pathString, NULL);
  1268         s_AndroidExternalFilesPath = SDL_strdup(path);
  1269         env->ReleaseStringUTFChars(pathString, path);
  1270     }
  1271     return s_AndroidExternalFilesPath;
  1272 }
  1273 
  1274 #endif /* __ANDROID__ */
  1275 
  1276 /* vi: set ts=4 sw=4 expandtab: */