src/core/android/SDL_android.cpp
author Gabriel Jacobo <gabomdq@gmail.com>
Mon, 01 Apr 2013 13:20:22 -0300
changeset 7039 f69b305b053d
parent 7037 3fedf1f25b94
child 7045 4be2c24b7de1
permissions -rw-r--r--
Backed out changeset: 9cef1005df5f
     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         return SDL_SetError("Failed to allocate enough JVM local references");
   782     }
   783 
   784     if (ctx) {
   785         if (release) {
   786             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   787         }
   788 
   789         if (ctx->hidden.androidio.assetFileDescriptorRef) {
   790             jobject inputStream = (jobject)ctx->hidden.androidio.assetFileDescriptorRef;
   791             jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   792                     "close", "()V");
   793             mEnv->CallVoidMethod(inputStream, mid);
   794             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.assetFileDescriptorRef);
   795             if (Android_JNI_ExceptionOccurred()) {
   796                 result = -1;
   797             }
   798         }
   799         else {
   800             jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
   801 
   802             // inputStream.close();
   803             jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   804                     "close", "()V");
   805             mEnv->CallVoidMethod(inputStream, mid);
   806             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   807             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
   808             if (Android_JNI_ExceptionOccurred()) {
   809                 result = -1;
   810             }
   811         }
   812 
   813         if (release) {
   814             SDL_FreeRW(ctx);
   815         }
   816     }
   817 
   818     return result;
   819 }
   820 
   821 
   822 extern "C" Sint64 Android_JNI_FileSize(SDL_RWops* ctx)
   823 {
   824     return ctx->hidden.androidio.size;
   825 }
   826 
   827 extern "C" Sint64 Android_JNI_FileSeek(SDL_RWops* ctx, Sint64 offset, int whence)
   828 {
   829     if (ctx->hidden.androidio.assetFileDescriptorRef) {
   830         switch (whence) {
   831             case RW_SEEK_SET:
   832                 if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
   833                 offset += ctx->hidden.androidio.offset;
   834                 break;
   835             case RW_SEEK_CUR:
   836                 offset += ctx->hidden.androidio.position;
   837                 if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
   838                 offset += ctx->hidden.androidio.offset;
   839                 break;
   840             case RW_SEEK_END:
   841                 offset = ctx->hidden.androidio.offset + ctx->hidden.androidio.size + offset;
   842                 break;
   843             default:
   844                 return SDL_SetError("Unknown value for 'whence'");
   845         }
   846         whence = SEEK_SET;
   847 
   848         off_t ret = lseek(ctx->hidden.androidio.fd, (off_t)offset, SEEK_SET);
   849         if (ret == -1) return -1;
   850         ctx->hidden.androidio.position = ret - ctx->hidden.androidio.offset;
   851     } else {
   852         Sint64 newPosition;
   853 
   854         switch (whence) {
   855             case RW_SEEK_SET:
   856                 newPosition = offset;
   857                 break;
   858             case RW_SEEK_CUR:
   859                 newPosition = ctx->hidden.androidio.position + offset;
   860                 break;
   861             case RW_SEEK_END:
   862                 newPosition = ctx->hidden.androidio.size + offset;
   863                 break;
   864             default:
   865                 return SDL_SetError("Unknown value for 'whence'");
   866         }
   867 
   868         /* Validate the new position */
   869         if (newPosition < 0) {
   870             return SDL_Error(SDL_EFSEEK);
   871         }
   872         if (newPosition > ctx->hidden.androidio.size) {
   873             newPosition = ctx->hidden.androidio.size;
   874         }
   875 
   876         Sint64 movement = newPosition - ctx->hidden.androidio.position;
   877         if (movement > 0) {
   878             unsigned char buffer[4096];
   879 
   880             // The easy case where we're seeking forwards
   881             while (movement > 0) {
   882                 Sint64 amount = sizeof (buffer);
   883                 if (amount > movement) {
   884                     amount = movement;
   885                 }
   886                 size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount);
   887                 if (result <= 0) {
   888                     // Failed to read/skip the required amount, so fail
   889                     return -1;
   890                 }
   891 
   892                 movement -= result;
   893             }
   894 
   895         } else if (movement < 0) {
   896             // We can't seek backwards so we have to reopen the file and seek
   897             // forwards which obviously isn't very efficient
   898             Android_JNI_FileClose(ctx, false);
   899             Android_JNI_FileOpen(ctx);
   900             Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
   901         }
   902     }
   903 
   904     return ctx->hidden.androidio.position;
   905     
   906 }
   907 
   908 extern "C" int Android_JNI_FileClose(SDL_RWops* ctx)
   909 {
   910     return Android_JNI_FileClose(ctx, true);
   911 }
   912 
   913 // returns a new global reference which needs to be released later
   914 static jobject Android_JNI_GetSystemServiceObject(const char* name)
   915 {
   916     LocalReferenceHolder refs(__FUNCTION__);
   917     JNIEnv* env = Android_JNI_GetEnv();
   918     if (!refs.init(env)) {
   919         return NULL;
   920     }
   921 
   922     jstring service = env->NewStringUTF(name);
   923 
   924     jmethodID mid;
   925 
   926     mid = env->GetStaticMethodID(mActivityClass, "getContext", "()Landroid/content/Context;");
   927     jobject context = env->CallStaticObjectMethod(mActivityClass, mid);
   928 
   929     mid = env->GetMethodID(mActivityClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
   930     jobject manager = env->CallObjectMethod(context, mid, service);
   931 
   932     env->DeleteLocalRef(service);
   933 
   934     return manager ? env->NewGlobalRef(manager) : NULL;
   935 }
   936 
   937 #define SETUP_CLIPBOARD(error) \
   938     LocalReferenceHolder refs(__FUNCTION__); \
   939     JNIEnv* env = Android_JNI_GetEnv(); \
   940     if (!refs.init(env)) { \
   941         return error; \
   942     } \
   943     jobject clipboard = Android_JNI_GetSystemServiceObject("clipboard"); \
   944     if (!clipboard) { \
   945         return error; \
   946     }
   947 
   948 extern "C" int Android_JNI_SetClipboardText(const char* text)
   949 {
   950     SETUP_CLIPBOARD(-1)
   951 
   952     jmethodID mid = env->GetMethodID(env->GetObjectClass(clipboard), "setText", "(Ljava/lang/CharSequence;)V");
   953     jstring string = env->NewStringUTF(text);
   954     env->CallVoidMethod(clipboard, mid, string);
   955     env->DeleteGlobalRef(clipboard);
   956     env->DeleteLocalRef(string);
   957     return 0;
   958 }
   959 
   960 extern "C" char* Android_JNI_GetClipboardText()
   961 {
   962     SETUP_CLIPBOARD(SDL_strdup(""))
   963 
   964     jmethodID mid = env->GetMethodID(env->GetObjectClass(clipboard), "getText", "()Ljava/lang/CharSequence;");
   965     jobject sequence = env->CallObjectMethod(clipboard, mid);
   966     env->DeleteGlobalRef(clipboard);
   967     if (sequence) {
   968         mid = env->GetMethodID(env->GetObjectClass(sequence), "toString", "()Ljava/lang/String;");
   969         jstring string = reinterpret_cast<jstring>(env->CallObjectMethod(sequence, mid));
   970         const char* utf = env->GetStringUTFChars(string, 0);
   971         if (utf) {
   972             char* text = SDL_strdup(utf);
   973             env->ReleaseStringUTFChars(string, utf);
   974             return text;
   975         }
   976     }
   977     return SDL_strdup("");
   978 }
   979 
   980 extern "C" SDL_bool Android_JNI_HasClipboardText()
   981 {
   982     SETUP_CLIPBOARD(SDL_FALSE)
   983 
   984     jmethodID mid = env->GetMethodID(env->GetObjectClass(clipboard), "hasText", "()Z");
   985     jboolean has = env->CallBooleanMethod(clipboard, mid);
   986     env->DeleteGlobalRef(clipboard);
   987     return has ? SDL_TRUE : SDL_FALSE;
   988 }
   989 
   990 
   991 // returns 0 on success or -1 on error (others undefined then)
   992 // returns truthy or falsy value in plugged, charged and battery
   993 // returns the value in seconds and percent or -1 if not available
   994 extern "C" int Android_JNI_GetPowerInfo(int* plugged, int* charged, int* battery, int* seconds, int* percent)
   995 {
   996     LocalReferenceHolder refs(__FUNCTION__);
   997     JNIEnv* env = Android_JNI_GetEnv();
   998     if (!refs.init(env)) {
   999         return -1;
  1000     }
  1001 
  1002     jmethodID mid;
  1003 
  1004     mid = env->GetStaticMethodID(mActivityClass, "getContext", "()Landroid/content/Context;");
  1005     jobject context = env->CallStaticObjectMethod(mActivityClass, mid);
  1006 
  1007     jstring action = env->NewStringUTF("android.intent.action.BATTERY_CHANGED");
  1008 
  1009     jclass cls = env->FindClass("android/content/IntentFilter");
  1010 
  1011     mid = env->GetMethodID(cls, "<init>", "(Ljava/lang/String;)V");
  1012     jobject filter = env->NewObject(cls, mid, action);
  1013 
  1014     env->DeleteLocalRef(action);
  1015 
  1016     mid = env->GetMethodID(mActivityClass, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;");
  1017     jobject intent = env->CallObjectMethod(context, mid, NULL, filter);
  1018 
  1019     env->DeleteLocalRef(filter);
  1020 
  1021     cls = env->GetObjectClass(intent);
  1022 
  1023     jstring iname;
  1024     jmethodID imid = env->GetMethodID(cls, "getIntExtra", "(Ljava/lang/String;I)I");
  1025 
  1026 #define GET_INT_EXTRA(var, key) \
  1027     iname = env->NewStringUTF(key); \
  1028     int var = env->CallIntMethod(intent, imid, iname, -1); \
  1029     env->DeleteLocalRef(iname);
  1030 
  1031     jstring bname;
  1032     jmethodID bmid = env->GetMethodID(cls, "getBooleanExtra", "(Ljava/lang/String;Z)Z");
  1033 
  1034 #define GET_BOOL_EXTRA(var, key) \
  1035     bname = env->NewStringUTF(key); \
  1036     int var = env->CallBooleanMethod(intent, bmid, bname, JNI_FALSE); \
  1037     env->DeleteLocalRef(bname);
  1038 
  1039     if (plugged) {
  1040         GET_INT_EXTRA(plug, "plugged") // == BatteryManager.EXTRA_PLUGGED (API 5)
  1041         if (plug == -1) {
  1042             return -1;
  1043         }
  1044         // 1 == BatteryManager.BATTERY_PLUGGED_AC
  1045         // 2 == BatteryManager.BATTERY_PLUGGED_USB
  1046         *plugged = (0 < plug) ? 1 : 0;
  1047     }
  1048 
  1049     if (charged) {
  1050         GET_INT_EXTRA(status, "status") // == BatteryManager.EXTRA_STATUS (API 5)
  1051         if (status == -1) {
  1052             return -1;
  1053         }
  1054         // 5 == BatteryManager.BATTERY_STATUS_FULL
  1055         *charged = (status == 5) ? 1 : 0;
  1056     }
  1057 
  1058     if (battery) {
  1059         GET_BOOL_EXTRA(present, "present") // == BatteryManager.EXTRA_PRESENT (API 5)
  1060         *battery = present ? 1 : 0;
  1061     }
  1062 
  1063     if (seconds) {
  1064         *seconds = -1; // not possible
  1065     }
  1066 
  1067     if (percent) {
  1068         GET_INT_EXTRA(level, "level") // == BatteryManager.EXTRA_LEVEL (API 5)
  1069         GET_INT_EXTRA(scale, "scale") // == BatteryManager.EXTRA_SCALE (API 5)
  1070         if ((level == -1) || (scale == -1)) {
  1071             return -1;
  1072         }
  1073         *percent = level * 100 / scale;
  1074     }
  1075 
  1076     env->DeleteLocalRef(intent);
  1077 
  1078     return 0;
  1079 }
  1080 
  1081 // sends message to be handled on the UI event dispatch thread
  1082 extern "C" int Android_JNI_SendMessage(int command, int param)
  1083 {
  1084     JNIEnv *env = Android_JNI_GetEnv();
  1085     if (!env) {
  1086         return -1;
  1087     }
  1088     jmethodID mid = env->GetStaticMethodID(mActivityClass, "sendMessage", "(II)V");
  1089     if (!mid) {
  1090         return -1;
  1091     }
  1092     env->CallStaticVoidMethod(mActivityClass, mid, command, param);
  1093     return 0;
  1094 }
  1095 
  1096 extern "C" void Android_JNI_ShowTextInput(SDL_Rect *inputRect)
  1097 {
  1098     JNIEnv *env = Android_JNI_GetEnv();
  1099     if (!env) {
  1100         return;
  1101     }
  1102 
  1103     jmethodID mid = env->GetStaticMethodID(mActivityClass, "showTextInput", "(IIII)V");
  1104     if (!mid) {
  1105         return;
  1106     }
  1107     env->CallStaticVoidMethod( mActivityClass, mid,
  1108                                inputRect->x,
  1109                                inputRect->y,
  1110                                inputRect->w,
  1111                                inputRect->h );
  1112 }
  1113 
  1114 extern "C" void Android_JNI_HideTextInput()
  1115 {
  1116     // has to match Activity constant
  1117     const int COMMAND_TEXTEDIT_HIDE = 3;
  1118     Android_JNI_SendMessage(COMMAND_TEXTEDIT_HIDE, 0);
  1119 }
  1120 
  1121 //////////////////////////////////////////////////////////////////////////////
  1122 //
  1123 // Functions exposed to SDL applications in SDL_system.h
  1124 //
  1125 
  1126 extern "C" void *SDL_AndroidGetJNIEnv()
  1127 {
  1128     return Android_JNI_GetEnv();
  1129 }
  1130 
  1131 extern "C" void *SDL_AndroidGetActivity()
  1132 {
  1133     LocalReferenceHolder refs(__FUNCTION__);
  1134     jmethodID mid;
  1135 
  1136     JNIEnv *env = Android_JNI_GetEnv();
  1137     if (!refs.init(env)) {
  1138         return NULL;
  1139     }
  1140 
  1141     // return SDLActivity.getContext();
  1142     mid = env->GetStaticMethodID(mActivityClass,
  1143             "getContext","()Landroid/content/Context;");
  1144     return env->CallStaticObjectMethod(mActivityClass, mid);
  1145 }
  1146 
  1147 extern "C" const char * SDL_AndroidGetInternalStoragePath()
  1148 {
  1149     static char *s_AndroidInternalFilesPath = NULL;
  1150 
  1151     if (!s_AndroidInternalFilesPath) {
  1152         LocalReferenceHolder refs(__FUNCTION__);
  1153         jmethodID mid;
  1154         jobject context;
  1155         jobject fileObject;
  1156         jstring pathString;
  1157         const char *path;
  1158 
  1159         JNIEnv *env = Android_JNI_GetEnv();
  1160         if (!refs.init(env)) {
  1161             return NULL;
  1162         }
  1163 
  1164         // context = SDLActivity.getContext();
  1165         mid = env->GetStaticMethodID(mActivityClass,
  1166                 "getContext","()Landroid/content/Context;");
  1167         context = env->CallStaticObjectMethod(mActivityClass, mid);
  1168 
  1169         // fileObj = context.getFilesDir();
  1170         mid = env->GetMethodID(env->GetObjectClass(context),
  1171                 "getFilesDir", "()Ljava/io/File;");
  1172         fileObject = env->CallObjectMethod(context, mid);
  1173         if (!fileObject) {
  1174             SDL_SetError("Couldn't get internal directory");
  1175             return NULL;
  1176         }
  1177 
  1178         // path = fileObject.getAbsolutePath();
  1179         mid = env->GetMethodID(env->GetObjectClass(fileObject),
  1180                 "getAbsolutePath", "()Ljava/lang/String;");
  1181         pathString = (jstring)env->CallObjectMethod(fileObject, mid);
  1182 
  1183         path = env->GetStringUTFChars(pathString, NULL);
  1184         s_AndroidInternalFilesPath = SDL_strdup(path);
  1185         env->ReleaseStringUTFChars(pathString, path);
  1186     }
  1187     return s_AndroidInternalFilesPath;
  1188 }
  1189 
  1190 extern "C" int SDL_AndroidGetExternalStorageState()
  1191 {
  1192     LocalReferenceHolder refs(__FUNCTION__);
  1193     jmethodID mid;
  1194     jclass cls;
  1195     jstring stateString;
  1196     const char *state;
  1197     int stateFlags;
  1198 
  1199     JNIEnv *env = Android_JNI_GetEnv();
  1200     if (!refs.init(env)) {
  1201         return 0;
  1202     }
  1203 
  1204     cls = env->FindClass("android/os/Environment");
  1205     mid = env->GetStaticMethodID(cls,
  1206             "getExternalStorageState", "()Ljava/lang/String;");
  1207     stateString = (jstring)env->CallStaticObjectMethod(cls, mid);
  1208 
  1209     state = env->GetStringUTFChars(stateString, NULL);
  1210 
  1211     // Print an info message so people debugging know the storage state
  1212     __android_log_print(ANDROID_LOG_INFO, "SDL", "external storage state: %s", state);
  1213 
  1214     if (SDL_strcmp(state, "mounted") == 0) {
  1215         stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ |
  1216                      SDL_ANDROID_EXTERNAL_STORAGE_WRITE;
  1217     } else if (SDL_strcmp(state, "mounted_ro") == 0) {
  1218         stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ;
  1219     } else {
  1220         stateFlags = 0;
  1221     }
  1222     env->ReleaseStringUTFChars(stateString, state);
  1223 
  1224     return stateFlags;
  1225 }
  1226 
  1227 extern "C" const char * SDL_AndroidGetExternalStoragePath()
  1228 {
  1229     static char *s_AndroidExternalFilesPath = NULL;
  1230 
  1231     if (!s_AndroidExternalFilesPath) {
  1232         LocalReferenceHolder refs(__FUNCTION__);
  1233         jmethodID mid;
  1234         jobject context;
  1235         jobject fileObject;
  1236         jstring pathString;
  1237         const char *path;
  1238 
  1239         JNIEnv *env = Android_JNI_GetEnv();
  1240         if (!refs.init(env)) {
  1241             return NULL;
  1242         }
  1243 
  1244         // context = SDLActivity.getContext();
  1245         mid = env->GetStaticMethodID(mActivityClass,
  1246                 "getContext","()Landroid/content/Context;");
  1247         context = env->CallStaticObjectMethod(mActivityClass, mid);
  1248 
  1249         // fileObj = context.getExternalFilesDir();
  1250         mid = env->GetMethodID(env->GetObjectClass(context),
  1251                 "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;");
  1252         fileObject = env->CallObjectMethod(context, mid, NULL);
  1253         if (!fileObject) {
  1254             SDL_SetError("Couldn't get external directory");
  1255             return NULL;
  1256         }
  1257 
  1258         // path = fileObject.getAbsolutePath();
  1259         mid = env->GetMethodID(env->GetObjectClass(fileObject),
  1260                 "getAbsolutePath", "()Ljava/lang/String;");
  1261         pathString = (jstring)env->CallObjectMethod(fileObject, mid);
  1262 
  1263         path = env->GetStringUTFChars(pathString, NULL);
  1264         s_AndroidExternalFilesPath = SDL_strdup(path);
  1265         env->ReleaseStringUTFChars(pathString, path);
  1266     }
  1267     return s_AndroidExternalFilesPath;
  1268 }
  1269 
  1270 #endif /* __ANDROID__ */
  1271 
  1272 /* vi: set ts=4 sw=4 expandtab: */