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