src/core/android/SDL_android.cpp
author Philipp Wiesemann <philipp.wiesemann@arcor.de>
Sun, 07 Jul 2013 16:11:29 +0200
changeset 7368 0e12e09df086
parent 7246 be3beed886ea
permissions -rw-r--r--
Fixed SDL_RWread() returning -1 as unsigned instead of 0 if error on Android.
Found by Cppcheck (pointed out check of unsigned < 0 which was left in place).
     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 
    54 static void Android_JNI_ThreadDestroyed(void*);
    55 } // C
    56 
    57 /*******************************************************************************
    58  This file links the Java side of Android with libsdl
    59 *******************************************************************************/
    60 #include <jni.h>
    61 #include <android/log.h>
    62 
    63 
    64 /*******************************************************************************
    65                                Globals
    66 *******************************************************************************/
    67 static pthread_key_t mThreadKey;
    68 static JavaVM* mJavaVM;
    69 
    70 // Main activity
    71 static jclass mActivityClass;
    72 
    73 // method signatures
    74 static jmethodID midCreateGLContext;
    75 static jmethodID midFlipBuffers;
    76 static jmethodID midAudioInit;
    77 static jmethodID midAudioWriteShortBuffer;
    78 static jmethodID midAudioWriteByteBuffer;
    79 static jmethodID midAudioQuit;
    80 
    81 // Accelerometer data storage
    82 static float fLastAccelerometer[3];
    83 static bool bHasNewData;
    84 
    85 /*******************************************************************************
    86                  Functions called by JNI
    87 *******************************************************************************/
    88 
    89 // Library init
    90 extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
    91 {
    92     JNIEnv *env;
    93     mJavaVM = vm;
    94     LOGI("JNI_OnLoad called");
    95     if (mJavaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
    96         LOGE("Failed to get the environment using GetEnv()");
    97         return -1;
    98     }
    99     /*
   100      * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread
   101      * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this
   102      */
   103     if (pthread_key_create(&mThreadKey, Android_JNI_ThreadDestroyed)) {
   104         __android_log_print(ANDROID_LOG_ERROR, "SDL", "Error initializing pthread key");
   105     }
   106     else {
   107         Android_JNI_SetupThread();
   108     }
   109 
   110     return JNI_VERSION_1_4;
   111 }
   112 
   113 // Called before SDL_main() to initialize JNI bindings
   114 extern "C" void SDL_Android_Init(JNIEnv* mEnv, jclass cls)
   115 {
   116     __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init()");
   117 
   118     Android_JNI_SetupThread();
   119 
   120     mActivityClass = (jclass)mEnv->NewGlobalRef(cls);
   121 
   122     midCreateGLContext = mEnv->GetStaticMethodID(mActivityClass,
   123                                 "createGLContext","(II[I)Z");
   124     midFlipBuffers = mEnv->GetStaticMethodID(mActivityClass,
   125                                 "flipBuffers","()V");
   126     midAudioInit = mEnv->GetStaticMethodID(mActivityClass,
   127                                 "audioInit", "(IZZI)V");
   128     midAudioWriteShortBuffer = mEnv->GetStaticMethodID(mActivityClass,
   129                                 "audioWriteShortBuffer", "([S)V");
   130     midAudioWriteByteBuffer = mEnv->GetStaticMethodID(mActivityClass,
   131                                 "audioWriteByteBuffer", "([B)V");
   132     midAudioQuit = mEnv->GetStaticMethodID(mActivityClass,
   133                                 "audioQuit", "()V");
   134 
   135     bHasNewData = false;
   136 
   137     if(!midCreateGLContext || !midFlipBuffers || !midAudioInit ||
   138        !midAudioWriteShortBuffer || !midAudioWriteByteBuffer || !midAudioQuit) {
   139         __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL: Couldn't locate Java callbacks, check that they're named and typed correctly");
   140     }
   141     __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init() finished!");
   142 }
   143 
   144 // Resize
   145 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeResize(
   146                                     JNIEnv* env, jclass jcls,
   147                                     jint width, jint height, jint format)
   148 {
   149     Android_SetScreenResolution(width, height, format);
   150 }
   151 
   152 // Keydown
   153 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyDown(
   154                                     JNIEnv* env, jclass jcls, jint keycode)
   155 {
   156     Android_OnKeyDown(keycode);
   157 }
   158 
   159 // Keyup
   160 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyUp(
   161                                     JNIEnv* env, jclass jcls, jint keycode)
   162 {
   163     Android_OnKeyUp(keycode);
   164 }
   165 
   166 // Touch
   167 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeTouch(
   168                                     JNIEnv* env, jclass jcls,
   169                                     jint touch_device_id_in, jint pointer_finger_id_in,
   170                                     jint action, jfloat x, jfloat y, jfloat p)
   171 {
   172     Android_OnTouch(touch_device_id_in, pointer_finger_id_in, action, x, y, p);
   173 }
   174 
   175 // Accelerometer
   176 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeAccel(
   177                                     JNIEnv* env, jclass jcls,
   178                                     jfloat x, jfloat y, jfloat z)
   179 {
   180     fLastAccelerometer[0] = x;
   181     fLastAccelerometer[1] = y;
   182     fLastAccelerometer[2] = z;
   183     bHasNewData = true;
   184 }
   185 
   186 // Low memory
   187 extern "C" void Java_org_libsdl_app_SDLActivity_nativeLowMemory(
   188                                     JNIEnv* env, jclass cls)
   189 {
   190     SDL_SendAppEvent(SDL_APP_LOWMEMORY);
   191 }
   192 
   193 // Quit
   194 extern "C" void Java_org_libsdl_app_SDLActivity_nativeQuit(
   195                                     JNIEnv* env, jclass cls)
   196 {
   197     // Inject a SDL_QUIT event
   198     SDL_SendQuit();
   199     SDL_SendAppEvent(SDL_APP_TERMINATING);
   200 }
   201 
   202 // Pause
   203 extern "C" void Java_org_libsdl_app_SDLActivity_nativePause(
   204                                     JNIEnv* env, jclass cls)
   205 {
   206     if (Android_Window) {
   207         /* Signal the pause semaphore so the event loop knows to pause and (optionally) block itself */
   208         if (!SDL_SemValue(Android_PauseSem)) SDL_SemPost(Android_PauseSem);
   209         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_LOST, 0, 0);
   210         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_MINIMIZED, 0, 0);
   211     }
   212 
   213     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativePause()");
   214     SDL_SendAppEvent(SDL_APP_WILLENTERBACKGROUND);
   215     SDL_SendAppEvent(SDL_APP_DIDENTERBACKGROUND);
   216 }
   217 
   218 // Resume
   219 extern "C" void Java_org_libsdl_app_SDLActivity_nativeResume(
   220                                     JNIEnv* env, jclass cls)
   221 {
   222     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeResume()");
   223     SDL_SendAppEvent(SDL_APP_WILLENTERFOREGROUND);
   224     SDL_SendAppEvent(SDL_APP_DIDENTERFOREGROUND);
   225 
   226     if (Android_Window) {
   227         /* Signal the resume semaphore so the event loop knows to resume and restore the GL Context
   228          * We can't restore the GL Context here because it needs to be done on the SDL main thread
   229          * and this function will be called from the Java thread instead.
   230          */
   231         if (!SDL_SemValue(Android_ResumeSem)) SDL_SemPost(Android_ResumeSem);
   232         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_GAINED, 0, 0);
   233         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_RESTORED, 0, 0);
   234     }
   235 }
   236 
   237 extern "C" void Java_org_libsdl_app_SDLActivity_nativeRunAudioThread(
   238                                     JNIEnv* env, jclass cls)
   239 {
   240     /* This is the audio thread, with a different environment */
   241     Android_JNI_SetupThread();
   242 
   243     Android_RunAudioThread();
   244 }
   245 
   246 extern "C" void Java_org_libsdl_app_SDLInputConnection_nativeCommitText(
   247                                     JNIEnv* env, jclass cls,
   248                                     jstring text, jint newCursorPosition)
   249 {
   250     const char *utftext = env->GetStringUTFChars(text, NULL);
   251 
   252     SDL_SendKeyboardText(utftext);
   253 
   254     env->ReleaseStringUTFChars(text, utftext);
   255 }
   256 
   257 extern "C" void Java_org_libsdl_app_SDLInputConnection_nativeSetComposingText(
   258                                     JNIEnv* env, jclass cls,
   259                                     jstring text, jint newCursorPosition)
   260 {
   261     const char *utftext = env->GetStringUTFChars(text, NULL);
   262 
   263     SDL_SendEditingText(utftext, 0, 0);
   264 
   265     env->ReleaseStringUTFChars(text, utftext);
   266 }
   267 
   268 
   269 
   270 /*******************************************************************************
   271              Functions called by SDL into Java
   272 *******************************************************************************/
   273 
   274 class LocalReferenceHolder
   275 {
   276 private:
   277     static int s_active;
   278 
   279 public:
   280     static bool IsActive() {
   281         return s_active > 0;
   282     }
   283 
   284 public:
   285     LocalReferenceHolder(const char *func) : m_env(NULL), m_func(func) {
   286 #ifdef DEBUG_JNI
   287         SDL_Log("Entering function %s", m_func);
   288 #endif
   289     }
   290     ~LocalReferenceHolder() {
   291 #ifdef DEBUG_JNI
   292         SDL_Log("Leaving function %s", m_func);
   293 #endif
   294         if (m_env) {
   295             m_env->PopLocalFrame(NULL);
   296             --s_active;
   297         }
   298     }
   299 
   300     bool init(JNIEnv *env, jint capacity = 16) {
   301         if (env->PushLocalFrame(capacity) < 0) {
   302             SDL_SetError("Failed to allocate enough JVM local references");
   303             return false;
   304         }
   305         ++s_active;
   306         m_env = env;
   307         return true;
   308     }
   309 
   310 protected:
   311     JNIEnv *m_env;
   312     const char *m_func;
   313 };
   314 int LocalReferenceHolder::s_active;
   315 
   316 extern "C" SDL_bool Android_JNI_CreateContext(int majorVersion, int minorVersion,
   317                                 int red, int green, int blue, int alpha,
   318                                 int buffer, int depth, int stencil,
   319                                 int buffers, int samples)
   320 {
   321     JNIEnv *env = Android_JNI_GetEnv();
   322 
   323     jint attribs[] = {
   324         EGL_RED_SIZE, red,
   325         EGL_GREEN_SIZE, green,
   326         EGL_BLUE_SIZE, blue,
   327         EGL_ALPHA_SIZE, alpha,
   328         EGL_BUFFER_SIZE, buffer,
   329         EGL_DEPTH_SIZE, depth,
   330         EGL_STENCIL_SIZE, stencil,
   331         EGL_SAMPLE_BUFFERS, buffers,
   332         EGL_SAMPLES, samples,
   333         EGL_RENDERABLE_TYPE, (majorVersion == 1 ? EGL_OPENGL_ES_BIT : EGL_OPENGL_ES2_BIT),
   334         EGL_NONE
   335     };
   336     int len = SDL_arraysize(attribs);
   337 
   338     jintArray array;
   339 
   340     array = env->NewIntArray(len);
   341     env->SetIntArrayRegion(array, 0, len, attribs);
   342 
   343     jboolean success = env->CallStaticBooleanMethod(mActivityClass, midCreateGLContext, majorVersion, minorVersion, array);
   344 
   345     env->DeleteLocalRef(array);
   346 
   347     return success ? SDL_TRUE : SDL_FALSE;
   348 }
   349 
   350 extern "C" void Android_JNI_SwapWindow()
   351 {
   352     JNIEnv *mEnv = Android_JNI_GetEnv();
   353     mEnv->CallStaticVoidMethod(mActivityClass, midFlipBuffers);
   354 }
   355 
   356 extern "C" void Android_JNI_SetActivityTitle(const char *title)
   357 {
   358     jmethodID mid;
   359     JNIEnv *mEnv = Android_JNI_GetEnv();
   360     mid = mEnv->GetStaticMethodID(mActivityClass,"setActivityTitle","(Ljava/lang/String;)Z");
   361     if (mid) {
   362         jstring jtitle = reinterpret_cast<jstring>(mEnv->NewStringUTF(title));
   363         mEnv->CallStaticBooleanMethod(mActivityClass, mid, jtitle);
   364         mEnv->DeleteLocalRef(jtitle);
   365     }
   366 }
   367 
   368 extern "C" SDL_bool Android_JNI_GetAccelerometerValues(float values[3])
   369 {
   370     int i;
   371     SDL_bool retval = SDL_FALSE;
   372 
   373     if (bHasNewData) {
   374         for (i = 0; i < 3; ++i) {
   375             values[i] = fLastAccelerometer[i];
   376         }
   377         bHasNewData = false;
   378         retval = SDL_TRUE;
   379     }
   380 
   381     return retval;
   382 }
   383 
   384 static void Android_JNI_ThreadDestroyed(void* value) {
   385     /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */
   386     JNIEnv *env = (JNIEnv*) value;
   387     if (env != NULL) {
   388         mJavaVM->DetachCurrentThread();
   389         pthread_setspecific(mThreadKey, NULL);
   390     }
   391 }
   392 
   393 JNIEnv* Android_JNI_GetEnv(void) {
   394     /* From http://developer.android.com/guide/practices/jni.html
   395      * All threads are Linux threads, scheduled by the kernel.
   396      * They're usually started from managed code (using Thread.start), but they can also be created elsewhere and then
   397      * attached to the JavaVM. For example, a thread started with pthread_create can be attached with the
   398      * JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a thread is attached, it has no JNIEnv,
   399      * and cannot make JNI calls.
   400      * Attaching a natively-created thread causes a java.lang.Thread object to be constructed and added to the "main"
   401      * ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread on an already-attached thread
   402      * is a no-op.
   403      * Note: You can call this function any number of times for the same thread, there's no harm in it
   404      */
   405 
   406     JNIEnv *env;
   407     int status = mJavaVM->AttachCurrentThread(&env, NULL);
   408     if(status < 0) {
   409         LOGE("failed to attach current thread");
   410         return 0;
   411     }
   412 
   413     return env;
   414 }
   415 
   416 int Android_JNI_SetupThread(void) {
   417     /* From http://developer.android.com/guide/practices/jni.html
   418      * Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward,
   419      * in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be
   420      * called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific
   421      * to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.)
   422      * Note: The destructor is not called unless the stored value is != NULL
   423      * Note: You can call this function any number of times for the same thread, there's no harm in it
   424      *       (except for some lost CPU cycles)
   425      */
   426     JNIEnv *env = Android_JNI_GetEnv();
   427     pthread_setspecific(mThreadKey, (void*) env);
   428     return 1;
   429 }
   430 
   431 //
   432 // Audio support
   433 //
   434 static jboolean audioBuffer16Bit = JNI_FALSE;
   435 static jboolean audioBufferStereo = JNI_FALSE;
   436 static jobject audioBuffer = NULL;
   437 static void* audioBufferPinned = NULL;
   438 
   439 extern "C" int Android_JNI_OpenAudioDevice(int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
   440 {
   441     int audioBufferFrames;
   442 
   443     JNIEnv *env = Android_JNI_GetEnv();
   444 
   445     if (!env) {
   446         LOGE("callback_handler: failed to attach current thread");
   447     }
   448     Android_JNI_SetupThread();
   449 
   450     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device");
   451     audioBuffer16Bit = is16Bit;
   452     audioBufferStereo = channelCount > 1;
   453 
   454     env->CallStaticVoidMethod(mActivityClass, midAudioInit, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames);
   455 
   456     /* Allocating the audio buffer from the Java side and passing it as the return value for audioInit no longer works on
   457      * Android >= 4.2 due to a "stale global reference" error. So now we allocate this buffer directly from this side. */
   458 
   459     if (is16Bit) {
   460         jshortArray audioBufferLocal = env->NewShortArray(desiredBufferFrames * (audioBufferStereo ? 2 : 1));
   461         if (audioBufferLocal) {
   462             audioBuffer = env->NewGlobalRef(audioBufferLocal);
   463             env->DeleteLocalRef(audioBufferLocal);
   464         }
   465     }
   466     else {
   467         jbyteArray audioBufferLocal = env->NewByteArray(desiredBufferFrames * (audioBufferStereo ? 2 : 1));
   468         if (audioBufferLocal) {
   469             audioBuffer = env->NewGlobalRef(audioBufferLocal);
   470             env->DeleteLocalRef(audioBufferLocal);
   471         }
   472     }
   473 
   474     if (audioBuffer == NULL) {
   475         __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: could not allocate an audio buffer!");
   476         return 0;
   477     }
   478 
   479     jboolean isCopy = JNI_FALSE;
   480     if (audioBuffer16Bit) {
   481         audioBufferPinned = env->GetShortArrayElements((jshortArray)audioBuffer, &isCopy);
   482         audioBufferFrames = env->GetArrayLength((jshortArray)audioBuffer);
   483     } else {
   484         audioBufferPinned = env->GetByteArrayElements((jbyteArray)audioBuffer, &isCopy);
   485         audioBufferFrames = env->GetArrayLength((jbyteArray)audioBuffer);
   486     }
   487     if (audioBufferStereo) {
   488         audioBufferFrames /= 2;
   489     }
   490 
   491     return audioBufferFrames;
   492 }
   493 
   494 extern "C" void * Android_JNI_GetAudioBuffer()
   495 {
   496     return audioBufferPinned;
   497 }
   498 
   499 extern "C" void Android_JNI_WriteAudioBuffer()
   500 {
   501     JNIEnv *mAudioEnv = Android_JNI_GetEnv();
   502 
   503     if (audioBuffer16Bit) {
   504         mAudioEnv->ReleaseShortArrayElements((jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
   505         mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
   506     } else {
   507         mAudioEnv->ReleaseByteArrayElements((jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT);
   508         mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer);
   509     }
   510 
   511     /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
   512 }
   513 
   514 extern "C" void Android_JNI_CloseAudioDevice()
   515 {
   516     JNIEnv *env = Android_JNI_GetEnv();
   517 
   518     env->CallStaticVoidMethod(mActivityClass, midAudioQuit);
   519 
   520     if (audioBuffer) {
   521         env->DeleteGlobalRef(audioBuffer);
   522         audioBuffer = NULL;
   523         audioBufferPinned = NULL;
   524     }
   525 }
   526 
   527 // Test for an exception and call SDL_SetError with its detail if one occurs
   528 // If optional parameter silent is truthy then SDL_SetError() is not called.
   529 static bool Android_JNI_ExceptionOccurred(bool silent = false)
   530 {
   531     SDL_assert(LocalReferenceHolder::IsActive());
   532     JNIEnv *mEnv = Android_JNI_GetEnv();
   533 
   534     jthrowable exception = mEnv->ExceptionOccurred();
   535     if (exception != NULL) {
   536         jmethodID mid;
   537 
   538         // Until this happens most JNI operations have undefined behaviour
   539         mEnv->ExceptionClear();
   540 
   541         if (!silent) {
   542             jclass exceptionClass = mEnv->GetObjectClass(exception);
   543             jclass classClass = mEnv->FindClass("java/lang/Class");
   544 
   545             mid = mEnv->GetMethodID(classClass, "getName", "()Ljava/lang/String;");
   546             jstring exceptionName = (jstring)mEnv->CallObjectMethod(exceptionClass, mid);
   547             const char* exceptionNameUTF8 = mEnv->GetStringUTFChars(exceptionName, 0);
   548 
   549             mid = mEnv->GetMethodID(exceptionClass, "getMessage", "()Ljava/lang/String;");
   550             jstring exceptionMessage = (jstring)mEnv->CallObjectMethod(exception, mid);
   551 
   552             if (exceptionMessage != NULL) {
   553                 const char* exceptionMessageUTF8 = mEnv->GetStringUTFChars(exceptionMessage, 0);
   554                 SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
   555                 mEnv->ReleaseStringUTFChars(exceptionMessage, exceptionMessageUTF8);
   556             } else {
   557                 SDL_SetError("%s", exceptionNameUTF8);
   558             }
   559 
   560             mEnv->ReleaseStringUTFChars(exceptionName, exceptionNameUTF8);
   561         }
   562 
   563         return true;
   564     }
   565 
   566     return false;
   567 }
   568 
   569 static int Android_JNI_FileOpen(SDL_RWops* ctx)
   570 {
   571     LocalReferenceHolder refs(__FUNCTION__);
   572     int result = 0;
   573 
   574     jmethodID mid;
   575     jobject context;
   576     jobject assetManager;
   577     jobject inputStream;
   578     jclass channels;
   579     jobject readableByteChannel;
   580     jstring fileNameJString;
   581     jobject fd;
   582     jclass fdCls;
   583     jfieldID descriptor;
   584 
   585     JNIEnv *mEnv = Android_JNI_GetEnv();
   586     if (!refs.init(mEnv)) {
   587         goto failure;
   588     }
   589 
   590     fileNameJString = (jstring)ctx->hidden.androidio.fileNameRef;
   591     ctx->hidden.androidio.position = 0;
   592 
   593     // context = SDLActivity.getContext();
   594     mid = mEnv->GetStaticMethodID(mActivityClass,
   595             "getContext","()Landroid/content/Context;");
   596     context = mEnv->CallStaticObjectMethod(mActivityClass, mid);
   597 
   598 
   599     // assetManager = context.getAssets();
   600     mid = mEnv->GetMethodID(mEnv->GetObjectClass(context),
   601             "getAssets", "()Landroid/content/res/AssetManager;");
   602     assetManager = mEnv->CallObjectMethod(context, mid);
   603 
   604     /* First let's try opening the file to obtain an AssetFileDescriptor.
   605     * This method reads the files directly from the APKs using standard *nix calls
   606     */
   607     mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager), "openFd", "(Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;");
   608     inputStream = mEnv->CallObjectMethod(assetManager, mid, fileNameJString);
   609     if (Android_JNI_ExceptionOccurred(true)) {
   610         goto fallback;
   611     }
   612 
   613     mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream), "getStartOffset", "()J");
   614     ctx->hidden.androidio.offset = mEnv->CallLongMethod(inputStream, mid);
   615     if (Android_JNI_ExceptionOccurred(true)) {
   616         goto fallback;
   617     }
   618 
   619     mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream), "getDeclaredLength", "()J");
   620     ctx->hidden.androidio.size = mEnv->CallLongMethod(inputStream, mid);
   621     if (Android_JNI_ExceptionOccurred(true)) {
   622         goto fallback;
   623     }
   624 
   625     mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream), "getFileDescriptor", "()Ljava/io/FileDescriptor;");
   626     fd = mEnv->CallObjectMethod(inputStream, mid);
   627     fdCls = mEnv->GetObjectClass(fd);
   628     descriptor = mEnv->GetFieldID(fdCls, "descriptor", "I");
   629     ctx->hidden.androidio.fd = mEnv->GetIntField(fd, descriptor);
   630     ctx->hidden.androidio.assetFileDescriptorRef = mEnv->NewGlobalRef(inputStream);
   631 
   632     // Seek to the correct offset in the file.
   633     lseek(ctx->hidden.androidio.fd, (off_t)ctx->hidden.androidio.offset, SEEK_SET);
   634 
   635     if (false) {
   636 fallback:
   637         // Disabled log message because of spam on the Nexus 7
   638         //__android_log_print(ANDROID_LOG_DEBUG, "SDL", "Falling back to legacy InputStream method for opening file");
   639 
   640         /* Try the old method using InputStream */
   641         ctx->hidden.androidio.assetFileDescriptorRef = NULL;
   642 
   643         // inputStream = assetManager.open(<filename>);
   644         mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager),
   645                 "open", "(Ljava/lang/String;I)Ljava/io/InputStream;");
   646         inputStream = mEnv->CallObjectMethod(assetManager, mid, fileNameJString, 1 /*ACCESS_RANDOM*/);
   647         if (Android_JNI_ExceptionOccurred()) {
   648             goto failure;
   649         }
   650 
   651         ctx->hidden.androidio.inputStreamRef = mEnv->NewGlobalRef(inputStream);
   652 
   653         // Despite all the visible documentation on [Asset]InputStream claiming
   654         // that the .available() method is not guaranteed to return the entire file
   655         // size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
   656         // android/apis/content/ReadAsset.java imply that Android's
   657         // AssetInputStream.available() /will/ always return the total file size
   658 
   659         // size = inputStream.available();
   660         mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   661                 "available", "()I");
   662         ctx->hidden.androidio.size = (long)mEnv->CallIntMethod(inputStream, mid);
   663         if (Android_JNI_ExceptionOccurred()) {
   664             goto failure;
   665         }
   666 
   667         // readableByteChannel = Channels.newChannel(inputStream);
   668         channels = mEnv->FindClass("java/nio/channels/Channels");
   669         mid = mEnv->GetStaticMethodID(channels,
   670                 "newChannel",
   671                 "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
   672         readableByteChannel = mEnv->CallStaticObjectMethod(
   673                 channels, mid, inputStream);
   674         if (Android_JNI_ExceptionOccurred()) {
   675             goto failure;
   676         }
   677 
   678         ctx->hidden.androidio.readableByteChannelRef =
   679             mEnv->NewGlobalRef(readableByteChannel);
   680 
   681         // Store .read id for reading purposes
   682         mid = mEnv->GetMethodID(mEnv->GetObjectClass(readableByteChannel),
   683                 "read", "(Ljava/nio/ByteBuffer;)I");
   684         ctx->hidden.androidio.readMethod = mid;
   685     }
   686 
   687     if (false) {
   688 failure:
   689         result = -1;
   690 
   691         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   692 
   693         if(ctx->hidden.androidio.inputStreamRef != NULL) {
   694             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   695         }
   696 
   697         if(ctx->hidden.androidio.readableByteChannelRef != NULL) {
   698             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
   699         }
   700 
   701         if(ctx->hidden.androidio.assetFileDescriptorRef != NULL) {
   702             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.assetFileDescriptorRef);
   703         }
   704 
   705     }
   706 
   707     return result;
   708 }
   709 
   710 extern "C" int Android_JNI_FileOpen(SDL_RWops* ctx,
   711         const char* fileName, const char*)
   712 {
   713     LocalReferenceHolder refs(__FUNCTION__);
   714     JNIEnv *mEnv = Android_JNI_GetEnv();
   715 
   716     if (!refs.init(mEnv)) {
   717         return -1;
   718     }
   719 
   720     if (!ctx) {
   721         return -1;
   722     }
   723 
   724     jstring fileNameJString = mEnv->NewStringUTF(fileName);
   725     ctx->hidden.androidio.fileNameRef = mEnv->NewGlobalRef(fileNameJString);
   726     ctx->hidden.androidio.inputStreamRef = NULL;
   727     ctx->hidden.androidio.readableByteChannelRef = NULL;
   728     ctx->hidden.androidio.readMethod = NULL;
   729     ctx->hidden.androidio.assetFileDescriptorRef = NULL;
   730 
   731     return Android_JNI_FileOpen(ctx);
   732 }
   733 
   734 extern "C" size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
   735         size_t size, size_t maxnum)
   736 {
   737     LocalReferenceHolder refs(__FUNCTION__);
   738 
   739     if (ctx->hidden.androidio.assetFileDescriptorRef) {
   740         size_t bytesMax = size * maxnum;
   741         if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && ctx->hidden.androidio.position + bytesMax > ctx->hidden.androidio.size) {
   742             bytesMax = ctx->hidden.androidio.size - ctx->hidden.androidio.position;
   743         }
   744         size_t result = read(ctx->hidden.androidio.fd, buffer, bytesMax );
   745         if (result > 0) {
   746             ctx->hidden.androidio.position += result;
   747             return result / size;
   748         }
   749         return 0;
   750     } else {
   751         jlong bytesRemaining = (jlong) (size * maxnum);
   752         jlong bytesMax = (jlong) (ctx->hidden.androidio.size -  ctx->hidden.androidio.position);
   753         int bytesRead = 0;
   754 
   755         /* Don't read more bytes than those that remain in the file, otherwise we get an exception */
   756         if (bytesRemaining >  bytesMax) bytesRemaining = bytesMax;
   757 
   758         JNIEnv *mEnv = Android_JNI_GetEnv();
   759         if (!refs.init(mEnv)) {
   760             return 0;
   761         }
   762 
   763         jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannelRef;
   764         jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
   765         jobject byteBuffer = mEnv->NewDirectByteBuffer(buffer, bytesRemaining);
   766 
   767         while (bytesRemaining > 0) {
   768             // result = readableByteChannel.read(...);
   769             int result = mEnv->CallIntMethod(readableByteChannel, readMethod, byteBuffer);
   770 
   771             if (Android_JNI_ExceptionOccurred()) {
   772                 return 0;
   773             }
   774 
   775             if (result < 0) {
   776                 break;
   777             }
   778 
   779             bytesRemaining -= result;
   780             bytesRead += result;
   781             ctx->hidden.androidio.position += result;
   782         }
   783         return bytesRead / size;
   784     }
   785 }
   786 
   787 extern "C" size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
   788         size_t size, size_t num)
   789 {
   790     SDL_SetError("Cannot write to Android package filesystem");
   791     return 0;
   792 }
   793 
   794 static int Android_JNI_FileClose(SDL_RWops* ctx, bool release)
   795 {
   796     LocalReferenceHolder refs(__FUNCTION__);
   797     int result = 0;
   798     JNIEnv *mEnv = Android_JNI_GetEnv();
   799 
   800     if (!refs.init(mEnv)) {
   801         return SDL_SetError("Failed to allocate enough JVM local references");
   802     }
   803 
   804     if (ctx) {
   805         if (release) {
   806             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   807         }
   808 
   809         if (ctx->hidden.androidio.assetFileDescriptorRef) {
   810             jobject inputStream = (jobject)ctx->hidden.androidio.assetFileDescriptorRef;
   811             jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   812                     "close", "()V");
   813             mEnv->CallVoidMethod(inputStream, mid);
   814             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.assetFileDescriptorRef);
   815             if (Android_JNI_ExceptionOccurred()) {
   816                 result = -1;
   817             }
   818         }
   819         else {
   820             jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
   821 
   822             // inputStream.close();
   823             jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   824                     "close", "()V");
   825             mEnv->CallVoidMethod(inputStream, mid);
   826             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   827             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
   828             if (Android_JNI_ExceptionOccurred()) {
   829                 result = -1;
   830             }
   831         }
   832 
   833         if (release) {
   834             SDL_FreeRW(ctx);
   835         }
   836     }
   837 
   838     return result;
   839 }
   840 
   841 
   842 extern "C" Sint64 Android_JNI_FileSize(SDL_RWops* ctx)
   843 {
   844     return ctx->hidden.androidio.size;
   845 }
   846 
   847 extern "C" Sint64 Android_JNI_FileSeek(SDL_RWops* ctx, Sint64 offset, int whence)
   848 {
   849     if (ctx->hidden.androidio.assetFileDescriptorRef) {
   850         switch (whence) {
   851             case RW_SEEK_SET:
   852                 if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
   853                 offset += ctx->hidden.androidio.offset;
   854                 break;
   855             case RW_SEEK_CUR:
   856                 offset += ctx->hidden.androidio.position;
   857                 if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
   858                 offset += ctx->hidden.androidio.offset;
   859                 break;
   860             case RW_SEEK_END:
   861                 offset = ctx->hidden.androidio.offset + ctx->hidden.androidio.size + offset;
   862                 break;
   863             default:
   864                 return SDL_SetError("Unknown value for 'whence'");
   865         }
   866         whence = SEEK_SET;
   867 
   868         off_t ret = lseek(ctx->hidden.androidio.fd, (off_t)offset, SEEK_SET);
   869         if (ret == -1) return -1;
   870         ctx->hidden.androidio.position = ret - ctx->hidden.androidio.offset;
   871     } else {
   872         Sint64 newPosition;
   873 
   874         switch (whence) {
   875             case RW_SEEK_SET:
   876                 newPosition = offset;
   877                 break;
   878             case RW_SEEK_CUR:
   879                 newPosition = ctx->hidden.androidio.position + offset;
   880                 break;
   881             case RW_SEEK_END:
   882                 newPosition = ctx->hidden.androidio.size + offset;
   883                 break;
   884             default:
   885                 return SDL_SetError("Unknown value for 'whence'");
   886         }
   887 
   888         /* Validate the new position */
   889         if (newPosition < 0) {
   890             return SDL_Error(SDL_EFSEEK);
   891         }
   892         if (newPosition > ctx->hidden.androidio.size) {
   893             newPosition = ctx->hidden.androidio.size;
   894         }
   895 
   896         Sint64 movement = newPosition - ctx->hidden.androidio.position;
   897         if (movement > 0) {
   898             unsigned char buffer[4096];
   899 
   900             // The easy case where we're seeking forwards
   901             while (movement > 0) {
   902                 Sint64 amount = sizeof (buffer);
   903                 if (amount > movement) {
   904                     amount = movement;
   905                 }
   906                 size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount);
   907                 if (result <= 0) {
   908                     // Failed to read/skip the required amount, so fail
   909                     return -1;
   910                 }
   911 
   912                 movement -= result;
   913             }
   914 
   915         } else if (movement < 0) {
   916             // We can't seek backwards so we have to reopen the file and seek
   917             // forwards which obviously isn't very efficient
   918             Android_JNI_FileClose(ctx, false);
   919             Android_JNI_FileOpen(ctx);
   920             Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
   921         }
   922     }
   923 
   924     return ctx->hidden.androidio.position;
   925 
   926 }
   927 
   928 extern "C" int Android_JNI_FileClose(SDL_RWops* ctx)
   929 {
   930     return Android_JNI_FileClose(ctx, true);
   931 }
   932 
   933 // returns a new global reference which needs to be released later
   934 static jobject Android_JNI_GetSystemServiceObject(const char* name)
   935 {
   936     LocalReferenceHolder refs(__FUNCTION__);
   937     JNIEnv* env = Android_JNI_GetEnv();
   938     if (!refs.init(env)) {
   939         return NULL;
   940     }
   941 
   942     jstring service = env->NewStringUTF(name);
   943 
   944     jmethodID mid;
   945 
   946     mid = env->GetStaticMethodID(mActivityClass, "getContext", "()Landroid/content/Context;");
   947     jobject context = env->CallStaticObjectMethod(mActivityClass, mid);
   948 
   949     mid = env->GetMethodID(mActivityClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
   950     jobject manager = env->CallObjectMethod(context, mid, service);
   951 
   952     env->DeleteLocalRef(service);
   953 
   954     return manager ? env->NewGlobalRef(manager) : NULL;
   955 }
   956 
   957 #define SETUP_CLIPBOARD(error) \
   958     LocalReferenceHolder refs(__FUNCTION__); \
   959     JNIEnv* env = Android_JNI_GetEnv(); \
   960     if (!refs.init(env)) { \
   961         return error; \
   962     } \
   963     jobject clipboard = Android_JNI_GetSystemServiceObject("clipboard"); \
   964     if (!clipboard) { \
   965         return error; \
   966     }
   967 
   968 extern "C" int Android_JNI_SetClipboardText(const char* text)
   969 {
   970     SETUP_CLIPBOARD(-1)
   971 
   972     jmethodID mid = env->GetMethodID(env->GetObjectClass(clipboard), "setText", "(Ljava/lang/CharSequence;)V");
   973     jstring string = env->NewStringUTF(text);
   974     env->CallVoidMethod(clipboard, mid, string);
   975     env->DeleteGlobalRef(clipboard);
   976     env->DeleteLocalRef(string);
   977     return 0;
   978 }
   979 
   980 extern "C" char* Android_JNI_GetClipboardText()
   981 {
   982     SETUP_CLIPBOARD(SDL_strdup(""))
   983 
   984     jmethodID mid = env->GetMethodID(env->GetObjectClass(clipboard), "getText", "()Ljava/lang/CharSequence;");
   985     jobject sequence = env->CallObjectMethod(clipboard, mid);
   986     env->DeleteGlobalRef(clipboard);
   987     if (sequence) {
   988         mid = env->GetMethodID(env->GetObjectClass(sequence), "toString", "()Ljava/lang/String;");
   989         jstring string = reinterpret_cast<jstring>(env->CallObjectMethod(sequence, mid));
   990         const char* utf = env->GetStringUTFChars(string, 0);
   991         if (utf) {
   992             char* text = SDL_strdup(utf);
   993             env->ReleaseStringUTFChars(string, utf);
   994             return text;
   995         }
   996     }
   997     return SDL_strdup("");
   998 }
   999 
  1000 extern "C" SDL_bool Android_JNI_HasClipboardText()
  1001 {
  1002     SETUP_CLIPBOARD(SDL_FALSE)
  1003 
  1004     jmethodID mid = env->GetMethodID(env->GetObjectClass(clipboard), "hasText", "()Z");
  1005     jboolean has = env->CallBooleanMethod(clipboard, mid);
  1006     env->DeleteGlobalRef(clipboard);
  1007     return has ? SDL_TRUE : SDL_FALSE;
  1008 }
  1009 
  1010 
  1011 // returns 0 on success or -1 on error (others undefined then)
  1012 // returns truthy or falsy value in plugged, charged and battery
  1013 // returns the value in seconds and percent or -1 if not available
  1014 extern "C" int Android_JNI_GetPowerInfo(int* plugged, int* charged, int* battery, int* seconds, int* percent)
  1015 {
  1016     LocalReferenceHolder refs(__FUNCTION__);
  1017     JNIEnv* env = Android_JNI_GetEnv();
  1018     if (!refs.init(env)) {
  1019         return -1;
  1020     }
  1021 
  1022     jmethodID mid;
  1023 
  1024     mid = env->GetStaticMethodID(mActivityClass, "getContext", "()Landroid/content/Context;");
  1025     jobject context = env->CallStaticObjectMethod(mActivityClass, mid);
  1026 
  1027     jstring action = env->NewStringUTF("android.intent.action.BATTERY_CHANGED");
  1028 
  1029     jclass cls = env->FindClass("android/content/IntentFilter");
  1030 
  1031     mid = env->GetMethodID(cls, "<init>", "(Ljava/lang/String;)V");
  1032     jobject filter = env->NewObject(cls, mid, action);
  1033 
  1034     env->DeleteLocalRef(action);
  1035 
  1036     mid = env->GetMethodID(mActivityClass, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;");
  1037     jobject intent = env->CallObjectMethod(context, mid, NULL, filter);
  1038 
  1039     env->DeleteLocalRef(filter);
  1040 
  1041     cls = env->GetObjectClass(intent);
  1042 
  1043     jstring iname;
  1044     jmethodID imid = env->GetMethodID(cls, "getIntExtra", "(Ljava/lang/String;I)I");
  1045 
  1046 #define GET_INT_EXTRA(var, key) \
  1047     iname = env->NewStringUTF(key); \
  1048     int var = env->CallIntMethod(intent, imid, iname, -1); \
  1049     env->DeleteLocalRef(iname);
  1050 
  1051     jstring bname;
  1052     jmethodID bmid = env->GetMethodID(cls, "getBooleanExtra", "(Ljava/lang/String;Z)Z");
  1053 
  1054 #define GET_BOOL_EXTRA(var, key) \
  1055     bname = env->NewStringUTF(key); \
  1056     int var = env->CallBooleanMethod(intent, bmid, bname, JNI_FALSE); \
  1057     env->DeleteLocalRef(bname);
  1058 
  1059     if (plugged) {
  1060         GET_INT_EXTRA(plug, "plugged") // == BatteryManager.EXTRA_PLUGGED (API 5)
  1061         if (plug == -1) {
  1062             return -1;
  1063         }
  1064         // 1 == BatteryManager.BATTERY_PLUGGED_AC
  1065         // 2 == BatteryManager.BATTERY_PLUGGED_USB
  1066         *plugged = (0 < plug) ? 1 : 0;
  1067     }
  1068 
  1069     if (charged) {
  1070         GET_INT_EXTRA(status, "status") // == BatteryManager.EXTRA_STATUS (API 5)
  1071         if (status == -1) {
  1072             return -1;
  1073         }
  1074         // 5 == BatteryManager.BATTERY_STATUS_FULL
  1075         *charged = (status == 5) ? 1 : 0;
  1076     }
  1077 
  1078     if (battery) {
  1079         GET_BOOL_EXTRA(present, "present") // == BatteryManager.EXTRA_PRESENT (API 5)
  1080         *battery = present ? 1 : 0;
  1081     }
  1082 
  1083     if (seconds) {
  1084         *seconds = -1; // not possible
  1085     }
  1086 
  1087     if (percent) {
  1088         GET_INT_EXTRA(level, "level") // == BatteryManager.EXTRA_LEVEL (API 5)
  1089         GET_INT_EXTRA(scale, "scale") // == BatteryManager.EXTRA_SCALE (API 5)
  1090         if ((level == -1) || (scale == -1)) {
  1091             return -1;
  1092         }
  1093         *percent = level * 100 / scale;
  1094     }
  1095 
  1096     env->DeleteLocalRef(intent);
  1097 
  1098     return 0;
  1099 }
  1100 
  1101 // sends message to be handled on the UI event dispatch thread
  1102 extern "C" int Android_JNI_SendMessage(int command, int param)
  1103 {
  1104     JNIEnv *env = Android_JNI_GetEnv();
  1105     if (!env) {
  1106         return -1;
  1107     }
  1108     jmethodID mid = env->GetStaticMethodID(mActivityClass, "sendMessage", "(II)Z");
  1109     if (!mid) {
  1110         return -1;
  1111     }
  1112     jboolean success = env->CallStaticBooleanMethod(mActivityClass, mid, command, param);
  1113     return success ? 0 : -1;
  1114 }
  1115 
  1116 extern "C" void Android_JNI_ShowTextInput(SDL_Rect *inputRect)
  1117 {
  1118     JNIEnv *env = Android_JNI_GetEnv();
  1119     if (!env) {
  1120         return;
  1121     }
  1122 
  1123     jmethodID mid = env->GetStaticMethodID(mActivityClass, "showTextInput", "(IIII)Z");
  1124     if (!mid) {
  1125         return;
  1126     }
  1127     env->CallStaticBooleanMethod( mActivityClass, mid,
  1128                                inputRect->x,
  1129                                inputRect->y,
  1130                                inputRect->w,
  1131                                inputRect->h );
  1132 }
  1133 
  1134 extern "C" void Android_JNI_HideTextInput()
  1135 {
  1136     // has to match Activity constant
  1137     const int COMMAND_TEXTEDIT_HIDE = 3;
  1138     Android_JNI_SendMessage(COMMAND_TEXTEDIT_HIDE, 0);
  1139 }
  1140 
  1141 //////////////////////////////////////////////////////////////////////////////
  1142 //
  1143 // Functions exposed to SDL applications in SDL_system.h
  1144 //
  1145 
  1146 extern "C" void *SDL_AndroidGetJNIEnv()
  1147 {
  1148     return Android_JNI_GetEnv();
  1149 }
  1150 
  1151 
  1152 
  1153 extern "C" void *SDL_AndroidGetActivity()
  1154 {
  1155     /* See SDL_system.h for caveats on using this function. */
  1156 
  1157     jmethodID mid;
  1158 
  1159     JNIEnv *env = Android_JNI_GetEnv();
  1160     if (!env) {
  1161         return NULL;
  1162     }
  1163 
  1164     // return SDLActivity.getContext();
  1165     mid = env->GetStaticMethodID(mActivityClass,
  1166             "getContext","()Landroid/content/Context;");
  1167     return env->CallStaticObjectMethod(mActivityClass, mid);
  1168 }
  1169 
  1170 extern "C" const char * SDL_AndroidGetInternalStoragePath()
  1171 {
  1172     static char *s_AndroidInternalFilesPath = NULL;
  1173 
  1174     if (!s_AndroidInternalFilesPath) {
  1175         LocalReferenceHolder refs(__FUNCTION__);
  1176         jmethodID mid;
  1177         jobject context;
  1178         jobject fileObject;
  1179         jstring pathString;
  1180         const char *path;
  1181 
  1182         JNIEnv *env = Android_JNI_GetEnv();
  1183         if (!refs.init(env)) {
  1184             return NULL;
  1185         }
  1186 
  1187         // context = SDLActivity.getContext();
  1188         mid = env->GetStaticMethodID(mActivityClass,
  1189                 "getContext","()Landroid/content/Context;");
  1190         context = env->CallStaticObjectMethod(mActivityClass, mid);
  1191 
  1192         // fileObj = context.getFilesDir();
  1193         mid = env->GetMethodID(env->GetObjectClass(context),
  1194                 "getFilesDir", "()Ljava/io/File;");
  1195         fileObject = env->CallObjectMethod(context, mid);
  1196         if (!fileObject) {
  1197             SDL_SetError("Couldn't get internal directory");
  1198             return NULL;
  1199         }
  1200 
  1201         // path = fileObject.getAbsolutePath();
  1202         mid = env->GetMethodID(env->GetObjectClass(fileObject),
  1203                 "getAbsolutePath", "()Ljava/lang/String;");
  1204         pathString = (jstring)env->CallObjectMethod(fileObject, mid);
  1205 
  1206         path = env->GetStringUTFChars(pathString, NULL);
  1207         s_AndroidInternalFilesPath = SDL_strdup(path);
  1208         env->ReleaseStringUTFChars(pathString, path);
  1209     }
  1210     return s_AndroidInternalFilesPath;
  1211 }
  1212 
  1213 extern "C" int SDL_AndroidGetExternalStorageState()
  1214 {
  1215     LocalReferenceHolder refs(__FUNCTION__);
  1216     jmethodID mid;
  1217     jclass cls;
  1218     jstring stateString;
  1219     const char *state;
  1220     int stateFlags;
  1221 
  1222     JNIEnv *env = Android_JNI_GetEnv();
  1223     if (!refs.init(env)) {
  1224         return 0;
  1225     }
  1226 
  1227     cls = env->FindClass("android/os/Environment");
  1228     mid = env->GetStaticMethodID(cls,
  1229             "getExternalStorageState", "()Ljava/lang/String;");
  1230     stateString = (jstring)env->CallStaticObjectMethod(cls, mid);
  1231 
  1232     state = env->GetStringUTFChars(stateString, NULL);
  1233 
  1234     // Print an info message so people debugging know the storage state
  1235     __android_log_print(ANDROID_LOG_INFO, "SDL", "external storage state: %s", state);
  1236 
  1237     if (SDL_strcmp(state, "mounted") == 0) {
  1238         stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ |
  1239                      SDL_ANDROID_EXTERNAL_STORAGE_WRITE;
  1240     } else if (SDL_strcmp(state, "mounted_ro") == 0) {
  1241         stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ;
  1242     } else {
  1243         stateFlags = 0;
  1244     }
  1245     env->ReleaseStringUTFChars(stateString, state);
  1246 
  1247     return stateFlags;
  1248 }
  1249 
  1250 extern "C" const char * SDL_AndroidGetExternalStoragePath()
  1251 {
  1252     static char *s_AndroidExternalFilesPath = NULL;
  1253 
  1254     if (!s_AndroidExternalFilesPath) {
  1255         LocalReferenceHolder refs(__FUNCTION__);
  1256         jmethodID mid;
  1257         jobject context;
  1258         jobject fileObject;
  1259         jstring pathString;
  1260         const char *path;
  1261 
  1262         JNIEnv *env = Android_JNI_GetEnv();
  1263         if (!refs.init(env)) {
  1264             return NULL;
  1265         }
  1266 
  1267         // context = SDLActivity.getContext();
  1268         mid = env->GetStaticMethodID(mActivityClass,
  1269                 "getContext","()Landroid/content/Context;");
  1270         context = env->CallStaticObjectMethod(mActivityClass, mid);
  1271 
  1272         // fileObj = context.getExternalFilesDir();
  1273         mid = env->GetMethodID(env->GetObjectClass(context),
  1274                 "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;");
  1275         fileObject = env->CallObjectMethod(context, mid, NULL);
  1276         if (!fileObject) {
  1277             SDL_SetError("Couldn't get external directory");
  1278             return NULL;
  1279         }
  1280 
  1281         // path = fileObject.getAbsolutePath();
  1282         mid = env->GetMethodID(env->GetObjectClass(fileObject),
  1283                 "getAbsolutePath", "()Ljava/lang/String;");
  1284         pathString = (jstring)env->CallObjectMethod(fileObject, mid);
  1285 
  1286         path = env->GetStringUTFChars(pathString, NULL);
  1287         s_AndroidExternalFilesPath = SDL_strdup(path);
  1288         env->ReleaseStringUTFChars(pathString, path);
  1289     }
  1290     return s_AndroidExternalFilesPath;
  1291 }
  1292 
  1293 #endif /* __ANDROID__ */
  1294 
  1295 /* vi: set ts=4 sw=4 expandtab: */