src/core/android/SDL_android.cpp
author Philipp Wiesemann <philipp.wiesemann@arcor.de>
Mon, 29 Apr 2013 23:54:22 +0200
changeset 7130 28df4f1a1712
parent 7095 79ca4d26d4b3
child 7149 534891ad4897
permissions -rw-r--r--
Removed unused variables from C source files.
     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     JNIEnv *env = Android_JNI_GetEnv();
   426 
   427     if (!env) {
   428         LOGE("callback_handler: failed to attach current thread");
   429     }
   430     Android_JNI_SetupThread();
   431 
   432     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device");
   433     audioBuffer16Bit = is16Bit;
   434     audioBufferStereo = channelCount > 1;
   435 
   436     env->CallStaticVoidMethod(mActivityClass, midAudioInit, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames);
   437 
   438     /* Allocating the audio buffer from the Java side and passing it as the return value for audioInit no longer works on
   439      * Android >= 4.2 due to a "stale global reference" error. So now we allocate this buffer directly from this side. */
   440     
   441     if (is16Bit) {
   442         jshortArray audioBufferLocal = env->NewShortArray(desiredBufferFrames * (audioBufferStereo ? 2 : 1));
   443         if (audioBufferLocal) {
   444             audioBuffer = env->NewGlobalRef(audioBufferLocal);
   445             env->DeleteLocalRef(audioBufferLocal);
   446         }
   447     }
   448     else {
   449         jbyteArray audioBufferLocal = env->NewByteArray(desiredBufferFrames * (audioBufferStereo ? 2 : 1));
   450         if (audioBufferLocal) {
   451             audioBuffer = env->NewGlobalRef(audioBufferLocal);
   452             env->DeleteLocalRef(audioBufferLocal);
   453         }
   454     }
   455 
   456     if (audioBuffer == NULL) {
   457         __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: could not allocate an audio buffer!");
   458         return 0;
   459     }
   460 
   461     jboolean isCopy = JNI_FALSE;
   462     if (audioBuffer16Bit) {
   463         audioBufferPinned = env->GetShortArrayElements((jshortArray)audioBuffer, &isCopy);
   464         audioBufferFrames = env->GetArrayLength((jshortArray)audioBuffer);
   465     } else {
   466         audioBufferPinned = env->GetByteArrayElements((jbyteArray)audioBuffer, &isCopy);
   467         audioBufferFrames = env->GetArrayLength((jbyteArray)audioBuffer);
   468     }
   469     if (audioBufferStereo) {
   470         audioBufferFrames /= 2;
   471     }
   472 
   473     return audioBufferFrames;
   474 }
   475 
   476 extern "C" void * Android_JNI_GetAudioBuffer()
   477 {
   478     return audioBufferPinned;
   479 }
   480 
   481 extern "C" void Android_JNI_WriteAudioBuffer()
   482 {
   483     JNIEnv *mAudioEnv = Android_JNI_GetEnv();
   484 
   485     if (audioBuffer16Bit) {
   486         mAudioEnv->ReleaseShortArrayElements((jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
   487         mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
   488     } else {
   489         mAudioEnv->ReleaseByteArrayElements((jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT);
   490         mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer);
   491     }
   492 
   493     /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
   494 }
   495 
   496 extern "C" void Android_JNI_CloseAudioDevice()
   497 {
   498     JNIEnv *env = Android_JNI_GetEnv();
   499 
   500     env->CallStaticVoidMethod(mActivityClass, midAudioQuit); 
   501 
   502     if (audioBuffer) {
   503         env->DeleteGlobalRef(audioBuffer);
   504         audioBuffer = NULL;
   505         audioBufferPinned = NULL;
   506     }
   507 }
   508 
   509 // Test for an exception and call SDL_SetError with its detail if one occurs
   510 // If optional parameter silent is truthy then SDL_SetError() is not called.
   511 static bool Android_JNI_ExceptionOccurred(bool silent = false)
   512 {
   513     SDL_assert(LocalReferenceHolder::IsActive());
   514     JNIEnv *mEnv = Android_JNI_GetEnv();
   515 
   516     jthrowable exception = mEnv->ExceptionOccurred();
   517     if (exception != NULL) {
   518         jmethodID mid;
   519 
   520         // Until this happens most JNI operations have undefined behaviour
   521         mEnv->ExceptionClear();
   522 
   523         if (!silent) {
   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(exceptionMessage, 0);
   536                 SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
   537                 mEnv->ReleaseStringUTFChars(exceptionMessage, exceptionMessageUTF8);
   538             } else {
   539                 SDL_SetError("%s", exceptionNameUTF8);
   540             }
   541 
   542             mEnv->ReleaseStringUTFChars(exceptionName, exceptionNameUTF8);
   543         }
   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(true)) {
   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(true)) {
   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(true)) {
   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 
  1132 
  1133 extern "C" void *SDL_AndroidGetActivity()
  1134 {
  1135     /* See SDL_system.h for caveats on using this function. */
  1136     
  1137     jmethodID mid;
  1138 
  1139     JNIEnv *env = Android_JNI_GetEnv();
  1140     if (!env) {
  1141         return NULL;
  1142     }
  1143 
  1144     // return SDLActivity.getContext();
  1145     mid = env->GetStaticMethodID(mActivityClass,
  1146             "getContext","()Landroid/content/Context;");
  1147     return env->CallStaticObjectMethod(mActivityClass, mid);
  1148 }
  1149 
  1150 extern "C" const char * SDL_AndroidGetInternalStoragePath()
  1151 {
  1152     static char *s_AndroidInternalFilesPath = NULL;
  1153 
  1154     if (!s_AndroidInternalFilesPath) {
  1155         LocalReferenceHolder refs(__FUNCTION__);
  1156         jmethodID mid;
  1157         jobject context;
  1158         jobject fileObject;
  1159         jstring pathString;
  1160         const char *path;
  1161 
  1162         JNIEnv *env = Android_JNI_GetEnv();
  1163         if (!refs.init(env)) {
  1164             return NULL;
  1165         }
  1166 
  1167         // context = SDLActivity.getContext();
  1168         mid = env->GetStaticMethodID(mActivityClass,
  1169                 "getContext","()Landroid/content/Context;");
  1170         context = env->CallStaticObjectMethod(mActivityClass, mid);
  1171 
  1172         // fileObj = context.getFilesDir();
  1173         mid = env->GetMethodID(env->GetObjectClass(context),
  1174                 "getFilesDir", "()Ljava/io/File;");
  1175         fileObject = env->CallObjectMethod(context, mid);
  1176         if (!fileObject) {
  1177             SDL_SetError("Couldn't get internal directory");
  1178             return NULL;
  1179         }
  1180 
  1181         // path = fileObject.getAbsolutePath();
  1182         mid = env->GetMethodID(env->GetObjectClass(fileObject),
  1183                 "getAbsolutePath", "()Ljava/lang/String;");
  1184         pathString = (jstring)env->CallObjectMethod(fileObject, mid);
  1185 
  1186         path = env->GetStringUTFChars(pathString, NULL);
  1187         s_AndroidInternalFilesPath = SDL_strdup(path);
  1188         env->ReleaseStringUTFChars(pathString, path);
  1189     }
  1190     return s_AndroidInternalFilesPath;
  1191 }
  1192 
  1193 extern "C" int SDL_AndroidGetExternalStorageState()
  1194 {
  1195     LocalReferenceHolder refs(__FUNCTION__);
  1196     jmethodID mid;
  1197     jclass cls;
  1198     jstring stateString;
  1199     const char *state;
  1200     int stateFlags;
  1201 
  1202     JNIEnv *env = Android_JNI_GetEnv();
  1203     if (!refs.init(env)) {
  1204         return 0;
  1205     }
  1206 
  1207     cls = env->FindClass("android/os/Environment");
  1208     mid = env->GetStaticMethodID(cls,
  1209             "getExternalStorageState", "()Ljava/lang/String;");
  1210     stateString = (jstring)env->CallStaticObjectMethod(cls, mid);
  1211 
  1212     state = env->GetStringUTFChars(stateString, NULL);
  1213 
  1214     // Print an info message so people debugging know the storage state
  1215     __android_log_print(ANDROID_LOG_INFO, "SDL", "external storage state: %s", state);
  1216 
  1217     if (SDL_strcmp(state, "mounted") == 0) {
  1218         stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ |
  1219                      SDL_ANDROID_EXTERNAL_STORAGE_WRITE;
  1220     } else if (SDL_strcmp(state, "mounted_ro") == 0) {
  1221         stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ;
  1222     } else {
  1223         stateFlags = 0;
  1224     }
  1225     env->ReleaseStringUTFChars(stateString, state);
  1226 
  1227     return stateFlags;
  1228 }
  1229 
  1230 extern "C" const char * SDL_AndroidGetExternalStoragePath()
  1231 {
  1232     static char *s_AndroidExternalFilesPath = NULL;
  1233 
  1234     if (!s_AndroidExternalFilesPath) {
  1235         LocalReferenceHolder refs(__FUNCTION__);
  1236         jmethodID mid;
  1237         jobject context;
  1238         jobject fileObject;
  1239         jstring pathString;
  1240         const char *path;
  1241 
  1242         JNIEnv *env = Android_JNI_GetEnv();
  1243         if (!refs.init(env)) {
  1244             return NULL;
  1245         }
  1246 
  1247         // context = SDLActivity.getContext();
  1248         mid = env->GetStaticMethodID(mActivityClass,
  1249                 "getContext","()Landroid/content/Context;");
  1250         context = env->CallStaticObjectMethod(mActivityClass, mid);
  1251 
  1252         // fileObj = context.getExternalFilesDir();
  1253         mid = env->GetMethodID(env->GetObjectClass(context),
  1254                 "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;");
  1255         fileObject = env->CallObjectMethod(context, mid, NULL);
  1256         if (!fileObject) {
  1257             SDL_SetError("Couldn't get external directory");
  1258             return NULL;
  1259         }
  1260 
  1261         // path = fileObject.getAbsolutePath();
  1262         mid = env->GetMethodID(env->GetObjectClass(fileObject),
  1263                 "getAbsolutePath", "()Ljava/lang/String;");
  1264         pathString = (jstring)env->CallObjectMethod(fileObject, mid);
  1265 
  1266         path = env->GetStringUTFChars(pathString, NULL);
  1267         s_AndroidExternalFilesPath = SDL_strdup(path);
  1268         env->ReleaseStringUTFChars(pathString, path);
  1269     }
  1270     return s_AndroidExternalFilesPath;
  1271 }
  1272 
  1273 #endif /* __ANDROID__ */
  1274 
  1275 /* vi: set ts=4 sw=4 expandtab: */