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