src/core/android/SDL_android.cpp
author Ryan C. Gordon <icculus@icculus.org>
Sun, 31 Mar 2013 12:48:50 -0400
changeset 7037 3fedf1f25b94
parent 7018 9cef1005df5f
child 7039 f69b305b053d
permissions -rw-r--r--
Make SDL_SetError and friends unconditionally return -1.

This lets us change things like this...

if (Failed) {
SDL_SetError("We failed");
return -1;
}

...into this...

if (Failed) {
return SDL_SetError("We failed");
}


Fixes Bugzilla #1778.
     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         return SDL_SetError("Failed to allocate enough JVM local references");
   815     }
   816 
   817     if (ctx) {
   818         if (release) {
   819             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   820         }
   821 
   822         if (ctx->hidden.androidio.assetFileDescriptorRef) {
   823             jobject inputStream = (jobject)ctx->hidden.androidio.assetFileDescriptorRef;
   824             jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   825                     "close", "()V");
   826             mEnv->CallVoidMethod(inputStream, mid);
   827             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.assetFileDescriptorRef);
   828             if (Android_JNI_ExceptionOccurred()) {
   829                 result = -1;
   830             }
   831         }
   832         else {
   833             jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
   834 
   835             // inputStream.close();
   836             jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   837                     "close", "()V");
   838             mEnv->CallVoidMethod(inputStream, mid);
   839             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   840             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
   841             if (Android_JNI_ExceptionOccurred()) {
   842                 result = -1;
   843             }
   844         }
   845 
   846         if (release) {
   847             SDL_FreeRW(ctx);
   848         }
   849     }
   850 
   851     return result;
   852 }
   853 
   854 
   855 extern "C" Sint64 Android_JNI_FileSize(SDL_RWops* ctx)
   856 {
   857     return ctx->hidden.androidio.size;
   858 }
   859 
   860 extern "C" Sint64 Android_JNI_FileSeek(SDL_RWops* ctx, Sint64 offset, int whence)
   861 {
   862     if (ctx->hidden.androidio.assetFileDescriptorRef) {
   863         switch (whence) {
   864             case RW_SEEK_SET:
   865                 if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
   866                 offset += ctx->hidden.androidio.offset;
   867                 break;
   868             case RW_SEEK_CUR:
   869                 offset += ctx->hidden.androidio.position;
   870                 if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
   871                 offset += ctx->hidden.androidio.offset;
   872                 break;
   873             case RW_SEEK_END:
   874                 offset = ctx->hidden.androidio.offset + ctx->hidden.androidio.size + offset;
   875                 break;
   876             default:
   877                 return SDL_SetError("Unknown value for 'whence'");
   878         }
   879         whence = SEEK_SET;
   880 
   881         off_t ret = lseek(ctx->hidden.androidio.fd, (off_t)offset, SEEK_SET);
   882         if (ret == -1) return -1;
   883         ctx->hidden.androidio.position = ret - ctx->hidden.androidio.offset;
   884     } else {
   885         Sint64 newPosition;
   886 
   887         switch (whence) {
   888             case RW_SEEK_SET:
   889                 newPosition = offset;
   890                 break;
   891             case RW_SEEK_CUR:
   892                 newPosition = ctx->hidden.androidio.position + offset;
   893                 break;
   894             case RW_SEEK_END:
   895                 newPosition = ctx->hidden.androidio.size + offset;
   896                 break;
   897             default:
   898                 return SDL_SetError("Unknown value for 'whence'");
   899         }
   900 
   901         /* Validate the new position */
   902         if (newPosition < 0) {
   903             return SDL_Error(SDL_EFSEEK);
   904         }
   905         if (newPosition > ctx->hidden.androidio.size) {
   906             newPosition = ctx->hidden.androidio.size;
   907         }
   908 
   909         Sint64 movement = newPosition - ctx->hidden.androidio.position;
   910         if (movement > 0) {
   911             unsigned char buffer[4096];
   912 
   913             // The easy case where we're seeking forwards
   914             while (movement > 0) {
   915                 Sint64 amount = sizeof (buffer);
   916                 if (amount > movement) {
   917                     amount = movement;
   918                 }
   919                 size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount);
   920                 if (result <= 0) {
   921                     // Failed to read/skip the required amount, so fail
   922                     return -1;
   923                 }
   924 
   925                 movement -= result;
   926             }
   927 
   928         } else if (movement < 0) {
   929             // We can't seek backwards so we have to reopen the file and seek
   930             // forwards which obviously isn't very efficient
   931             Android_JNI_FileClose(ctx, false);
   932             Android_JNI_FileOpen(ctx);
   933             Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
   934         }
   935     }
   936 
   937     return ctx->hidden.androidio.position;
   938     
   939 }
   940 
   941 extern "C" int Android_JNI_FileClose(SDL_RWops* ctx)
   942 {
   943     return Android_JNI_FileClose(ctx, true);
   944 }
   945 
   946 // returns a new global reference which needs to be released later
   947 static jobject Android_JNI_GetSystemServiceObject(const char* name)
   948 {
   949     LocalReferenceHolder refs(__FUNCTION__);
   950     JNIEnv* env = Android_JNI_GetEnv();
   951     if (!refs.init(env)) {
   952         return NULL;
   953     }
   954 
   955     jstring service = env->NewStringUTF(name);
   956 
   957     jmethodID mid;
   958 
   959     mid = env->GetStaticMethodID(mActivityClass, "getContext", "()Landroid/content/Context;");
   960     jobject context = env->CallStaticObjectMethod(mActivityClass, mid);
   961 
   962     mid = env->GetMethodID(mActivityClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
   963     jobject manager = env->CallObjectMethod(context, mid, service);
   964 
   965     env->DeleteLocalRef(service);
   966 
   967     return manager ? env->NewGlobalRef(manager) : NULL;
   968 }
   969 
   970 #define SETUP_CLIPBOARD(error) \
   971     LocalReferenceHolder refs(__FUNCTION__); \
   972     JNIEnv* env = Android_JNI_GetEnv(); \
   973     if (!refs.init(env)) { \
   974         return error; \
   975     } \
   976     jobject clipboard = Android_JNI_GetSystemServiceObject("clipboard"); \
   977     if (!clipboard) { \
   978         return error; \
   979     }
   980 
   981 extern "C" int Android_JNI_SetClipboardText(const char* text)
   982 {
   983     SETUP_CLIPBOARD(-1)
   984 
   985     jmethodID mid = env->GetMethodID(env->GetObjectClass(clipboard), "setText", "(Ljava/lang/CharSequence;)V");
   986     jstring string = env->NewStringUTF(text);
   987     env->CallVoidMethod(clipboard, mid, string);
   988     env->DeleteGlobalRef(clipboard);
   989     env->DeleteLocalRef(string);
   990     return 0;
   991 }
   992 
   993 extern "C" char* Android_JNI_GetClipboardText()
   994 {
   995     SETUP_CLIPBOARD(SDL_strdup(""))
   996 
   997     jmethodID mid = env->GetMethodID(env->GetObjectClass(clipboard), "getText", "()Ljava/lang/CharSequence;");
   998     jobject sequence = env->CallObjectMethod(clipboard, mid);
   999     env->DeleteGlobalRef(clipboard);
  1000     if (sequence) {
  1001         mid = env->GetMethodID(env->GetObjectClass(sequence), "toString", "()Ljava/lang/String;");
  1002         jstring string = reinterpret_cast<jstring>(env->CallObjectMethod(sequence, mid));
  1003         const char* utf = env->GetStringUTFChars(string, 0);
  1004         if (utf) {
  1005             char* text = SDL_strdup(utf);
  1006             env->ReleaseStringUTFChars(string, utf);
  1007             return text;
  1008         }
  1009     }
  1010     return SDL_strdup("");
  1011 }
  1012 
  1013 extern "C" SDL_bool Android_JNI_HasClipboardText()
  1014 {
  1015     SETUP_CLIPBOARD(SDL_FALSE)
  1016 
  1017     jmethodID mid = env->GetMethodID(env->GetObjectClass(clipboard), "hasText", "()Z");
  1018     jboolean has = env->CallBooleanMethod(clipboard, mid);
  1019     env->DeleteGlobalRef(clipboard);
  1020     return has ? SDL_TRUE : SDL_FALSE;
  1021 }
  1022 
  1023 
  1024 // returns 0 on success or -1 on error (others undefined then)
  1025 // returns truthy or falsy value in plugged, charged and battery
  1026 // returns the value in seconds and percent or -1 if not available
  1027 extern "C" int Android_JNI_GetPowerInfo(int* plugged, int* charged, int* battery, int* seconds, int* percent)
  1028 {
  1029     LocalReferenceHolder refs(__FUNCTION__);
  1030     JNIEnv* env = Android_JNI_GetEnv();
  1031     if (!refs.init(env)) {
  1032         return -1;
  1033     }
  1034 
  1035     jmethodID mid;
  1036 
  1037     mid = env->GetStaticMethodID(mActivityClass, "getContext", "()Landroid/content/Context;");
  1038     jobject context = env->CallStaticObjectMethod(mActivityClass, mid);
  1039 
  1040     jstring action = env->NewStringUTF("android.intent.action.BATTERY_CHANGED");
  1041 
  1042     jclass cls = env->FindClass("android/content/IntentFilter");
  1043 
  1044     mid = env->GetMethodID(cls, "<init>", "(Ljava/lang/String;)V");
  1045     jobject filter = env->NewObject(cls, mid, action);
  1046 
  1047     env->DeleteLocalRef(action);
  1048 
  1049     mid = env->GetMethodID(mActivityClass, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;");
  1050     jobject intent = env->CallObjectMethod(context, mid, NULL, filter);
  1051 
  1052     env->DeleteLocalRef(filter);
  1053 
  1054     cls = env->GetObjectClass(intent);
  1055 
  1056     jstring iname;
  1057     jmethodID imid = env->GetMethodID(cls, "getIntExtra", "(Ljava/lang/String;I)I");
  1058 
  1059 #define GET_INT_EXTRA(var, key) \
  1060     iname = env->NewStringUTF(key); \
  1061     int var = env->CallIntMethod(intent, imid, iname, -1); \
  1062     env->DeleteLocalRef(iname);
  1063 
  1064     jstring bname;
  1065     jmethodID bmid = env->GetMethodID(cls, "getBooleanExtra", "(Ljava/lang/String;Z)Z");
  1066 
  1067 #define GET_BOOL_EXTRA(var, key) \
  1068     bname = env->NewStringUTF(key); \
  1069     int var = env->CallBooleanMethod(intent, bmid, bname, JNI_FALSE); \
  1070     env->DeleteLocalRef(bname);
  1071 
  1072     if (plugged) {
  1073         GET_INT_EXTRA(plug, "plugged") // == BatteryManager.EXTRA_PLUGGED (API 5)
  1074         if (plug == -1) {
  1075             return -1;
  1076         }
  1077         // 1 == BatteryManager.BATTERY_PLUGGED_AC
  1078         // 2 == BatteryManager.BATTERY_PLUGGED_USB
  1079         *plugged = (0 < plug) ? 1 : 0;
  1080     }
  1081 
  1082     if (charged) {
  1083         GET_INT_EXTRA(status, "status") // == BatteryManager.EXTRA_STATUS (API 5)
  1084         if (status == -1) {
  1085             return -1;
  1086         }
  1087         // 5 == BatteryManager.BATTERY_STATUS_FULL
  1088         *charged = (status == 5) ? 1 : 0;
  1089     }
  1090 
  1091     if (battery) {
  1092         GET_BOOL_EXTRA(present, "present") // == BatteryManager.EXTRA_PRESENT (API 5)
  1093         *battery = present ? 1 : 0;
  1094     }
  1095 
  1096     if (seconds) {
  1097         *seconds = -1; // not possible
  1098     }
  1099 
  1100     if (percent) {
  1101         GET_INT_EXTRA(level, "level") // == BatteryManager.EXTRA_LEVEL (API 5)
  1102         GET_INT_EXTRA(scale, "scale") // == BatteryManager.EXTRA_SCALE (API 5)
  1103         if ((level == -1) || (scale == -1)) {
  1104             return -1;
  1105         }
  1106         *percent = level * 100 / scale;
  1107     }
  1108 
  1109     env->DeleteLocalRef(intent);
  1110 
  1111     return 0;
  1112 }
  1113 
  1114 // Initialize the joystick subsystem on the Java side
  1115 int Android_JNI_JoystickInit()
  1116 {
  1117     JNIEnv* env = Android_JNI_GetEnv();
  1118     if (!env) {
  1119         return -1;
  1120     }
  1121     jmethodID mid = env->GetStaticMethodID(mActivityClass, "joystickInit", "()V");
  1122     if (!mid) {
  1123         return -1;
  1124     }
  1125     env->CallStaticVoidMethod(mActivityClass, mid);
  1126     return 0;
  1127 }
  1128 
  1129 // Quit the joystick subsystem on the Java side
  1130 int Android_JNI_JoystickQuit()
  1131 {
  1132     JNIEnv* env = Android_JNI_GetEnv();
  1133     if (!env) {
  1134         return -1;
  1135     }
  1136     jmethodID mid = env->GetStaticMethodID(mActivityClass, "joystickQuit", "()V");
  1137     if (!mid) {
  1138         return -1;
  1139     }
  1140     env->CallStaticVoidMethod(mActivityClass, mid);
  1141     return 0;
  1142 }
  1143 
  1144 // return the total number of plugged in joysticks
  1145 extern "C" int Android_JNI_GetNumJoysticks()
  1146 {
  1147     JNIEnv* env = Android_JNI_GetEnv();
  1148     if (!env) {
  1149         return -1;
  1150     }
  1151     jmethodID mid = env->GetStaticMethodID(mActivityClass, "getNumJoysticks", "()I");
  1152     if (!mid) {
  1153         return -1;
  1154     }
  1155     
  1156     return env->CallStaticIntMethod(mActivityClass, mid);
  1157 }
  1158 
  1159 // Return the name of joystick number "index"
  1160 extern "C" char* Android_JNI_GetJoystickName(int index)
  1161 {
  1162     JNIEnv* env = Android_JNI_GetEnv();
  1163     if (!env) {
  1164         return SDL_strdup("");
  1165     }
  1166 
  1167     jmethodID mid = env->GetStaticMethodID(mActivityClass, "getJoystickName", "(I)Ljava/lang/String;");
  1168     if (!mid) {
  1169             return SDL_strdup("");
  1170     }
  1171     jstring string = reinterpret_cast<jstring>(env->CallStaticObjectMethod(mActivityClass, mid, index));
  1172     const char* utf = env->GetStringUTFChars(string, 0);
  1173     if (!utf) {
  1174             return SDL_strdup("");
  1175     }
  1176 
  1177     char* text = SDL_strdup(utf);
  1178     env->ReleaseStringUTFChars(string, utf);
  1179     return text;
  1180 }
  1181 
  1182 // return the number of axes in the given joystick
  1183 extern "C" int Android_JNI_GetJoystickNumOfAxes(int index)
  1184 {
  1185     JNIEnv* env = Android_JNI_GetEnv();
  1186     if (!env) {
  1187         return -1;
  1188     }
  1189     jmethodID mid = env->GetStaticMethodID(mActivityClass, "getJoystickNumOfAxes", "(I)I");
  1190     if (!mid) {
  1191         return -1;
  1192     }
  1193     
  1194     return env->CallStaticIntMethod(mActivityClass, mid, index);
  1195 }
  1196 
  1197 #if ENABLE_ACCELOMETER_AS_EMULATED_JOYSTICK
  1198 // Return the name of the default accelerometer
  1199 // This is much easier to be done with NDK than with JNI
  1200 extern "C" char* Android_GetAccelName()
  1201 {
  1202     ASensorManager* mSensorManager = ASensorManager_getInstance();
  1203     ASensor const* mAccelerometer = ASensorManager_getDefaultSensor(mSensorManager, ASENSOR_TYPE_ACCELEROMETER);
  1204 
  1205     return SDL_strdup(ASensor_getName(mAccelerometer));
  1206 }
  1207 #endif
  1208 
  1209 // sends message to be handled on the UI event dispatch thread
  1210 extern "C" int Android_JNI_SendMessage(int command, int param)
  1211 {
  1212     JNIEnv *env = Android_JNI_GetEnv();
  1213     if (!env) {
  1214         return -1;
  1215     }
  1216     jmethodID mid = env->GetStaticMethodID(mActivityClass, "sendMessage", "(II)V");
  1217     if (!mid) {
  1218         return -1;
  1219     }
  1220     env->CallStaticVoidMethod(mActivityClass, mid, command, param);
  1221     return 0;
  1222 }
  1223 
  1224 extern "C" void Android_JNI_ShowTextInput(SDL_Rect *inputRect)
  1225 {
  1226     JNIEnv *env = Android_JNI_GetEnv();
  1227     if (!env) {
  1228         return;
  1229     }
  1230 
  1231     jmethodID mid = env->GetStaticMethodID(mActivityClass, "showTextInput", "(IIII)V");
  1232     if (!mid) {
  1233         return;
  1234     }
  1235     env->CallStaticVoidMethod( mActivityClass, mid,
  1236                                inputRect->x,
  1237                                inputRect->y,
  1238                                inputRect->w,
  1239                                inputRect->h );
  1240 }
  1241 
  1242 extern "C" void Android_JNI_HideTextInput()
  1243 {
  1244     // has to match Activity constant
  1245     const int COMMAND_TEXTEDIT_HIDE = 3;
  1246     Android_JNI_SendMessage(COMMAND_TEXTEDIT_HIDE, 0);
  1247 }
  1248 
  1249 //////////////////////////////////////////////////////////////////////////////
  1250 //
  1251 // Functions exposed to SDL applications in SDL_system.h
  1252 //
  1253 
  1254 extern "C" void *SDL_AndroidGetJNIEnv()
  1255 {
  1256     return Android_JNI_GetEnv();
  1257 }
  1258 
  1259 extern "C" void *SDL_AndroidGetActivity()
  1260 {
  1261     LocalReferenceHolder refs(__FUNCTION__);
  1262     jmethodID mid;
  1263 
  1264     JNIEnv *env = Android_JNI_GetEnv();
  1265     if (!refs.init(env)) {
  1266         return NULL;
  1267     }
  1268 
  1269     // return SDLActivity.getContext();
  1270     mid = env->GetStaticMethodID(mActivityClass,
  1271             "getContext","()Landroid/content/Context;");
  1272     return env->CallStaticObjectMethod(mActivityClass, mid);
  1273 }
  1274 
  1275 extern "C" const char * SDL_AndroidGetInternalStoragePath()
  1276 {
  1277     static char *s_AndroidInternalFilesPath = NULL;
  1278 
  1279     if (!s_AndroidInternalFilesPath) {
  1280         LocalReferenceHolder refs(__FUNCTION__);
  1281         jmethodID mid;
  1282         jobject context;
  1283         jobject fileObject;
  1284         jstring pathString;
  1285         const char *path;
  1286 
  1287         JNIEnv *env = Android_JNI_GetEnv();
  1288         if (!refs.init(env)) {
  1289             return NULL;
  1290         }
  1291 
  1292         // context = SDLActivity.getContext();
  1293         mid = env->GetStaticMethodID(mActivityClass,
  1294                 "getContext","()Landroid/content/Context;");
  1295         context = env->CallStaticObjectMethod(mActivityClass, mid);
  1296 
  1297         // fileObj = context.getFilesDir();
  1298         mid = env->GetMethodID(env->GetObjectClass(context),
  1299                 "getFilesDir", "()Ljava/io/File;");
  1300         fileObject = env->CallObjectMethod(context, mid);
  1301         if (!fileObject) {
  1302             SDL_SetError("Couldn't get internal directory");
  1303             return NULL;
  1304         }
  1305 
  1306         // path = fileObject.getAbsolutePath();
  1307         mid = env->GetMethodID(env->GetObjectClass(fileObject),
  1308                 "getAbsolutePath", "()Ljava/lang/String;");
  1309         pathString = (jstring)env->CallObjectMethod(fileObject, mid);
  1310 
  1311         path = env->GetStringUTFChars(pathString, NULL);
  1312         s_AndroidInternalFilesPath = SDL_strdup(path);
  1313         env->ReleaseStringUTFChars(pathString, path);
  1314     }
  1315     return s_AndroidInternalFilesPath;
  1316 }
  1317 
  1318 extern "C" int SDL_AndroidGetExternalStorageState()
  1319 {
  1320     LocalReferenceHolder refs(__FUNCTION__);
  1321     jmethodID mid;
  1322     jclass cls;
  1323     jstring stateString;
  1324     const char *state;
  1325     int stateFlags;
  1326 
  1327     JNIEnv *env = Android_JNI_GetEnv();
  1328     if (!refs.init(env)) {
  1329         return 0;
  1330     }
  1331 
  1332     cls = env->FindClass("android/os/Environment");
  1333     mid = env->GetStaticMethodID(cls,
  1334             "getExternalStorageState", "()Ljava/lang/String;");
  1335     stateString = (jstring)env->CallStaticObjectMethod(cls, mid);
  1336 
  1337     state = env->GetStringUTFChars(stateString, NULL);
  1338 
  1339     // Print an info message so people debugging know the storage state
  1340     __android_log_print(ANDROID_LOG_INFO, "SDL", "external storage state: %s", state);
  1341 
  1342     if (SDL_strcmp(state, "mounted") == 0) {
  1343         stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ |
  1344                      SDL_ANDROID_EXTERNAL_STORAGE_WRITE;
  1345     } else if (SDL_strcmp(state, "mounted_ro") == 0) {
  1346         stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ;
  1347     } else {
  1348         stateFlags = 0;
  1349     }
  1350     env->ReleaseStringUTFChars(stateString, state);
  1351 
  1352     return stateFlags;
  1353 }
  1354 
  1355 extern "C" const char * SDL_AndroidGetExternalStoragePath()
  1356 {
  1357     static char *s_AndroidExternalFilesPath = NULL;
  1358 
  1359     if (!s_AndroidExternalFilesPath) {
  1360         LocalReferenceHolder refs(__FUNCTION__);
  1361         jmethodID mid;
  1362         jobject context;
  1363         jobject fileObject;
  1364         jstring pathString;
  1365         const char *path;
  1366 
  1367         JNIEnv *env = Android_JNI_GetEnv();
  1368         if (!refs.init(env)) {
  1369             return NULL;
  1370         }
  1371 
  1372         // context = SDLActivity.getContext();
  1373         mid = env->GetStaticMethodID(mActivityClass,
  1374                 "getContext","()Landroid/content/Context;");
  1375         context = env->CallStaticObjectMethod(mActivityClass, mid);
  1376 
  1377         // fileObj = context.getExternalFilesDir();
  1378         mid = env->GetMethodID(env->GetObjectClass(context),
  1379                 "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;");
  1380         fileObject = env->CallObjectMethod(context, mid, NULL);
  1381         if (!fileObject) {
  1382             SDL_SetError("Couldn't get external directory");
  1383             return NULL;
  1384         }
  1385 
  1386         // path = fileObject.getAbsolutePath();
  1387         mid = env->GetMethodID(env->GetObjectClass(fileObject),
  1388                 "getAbsolutePath", "()Ljava/lang/String;");
  1389         pathString = (jstring)env->CallObjectMethod(fileObject, mid);
  1390 
  1391         path = env->GetStringUTFChars(pathString, NULL);
  1392         s_AndroidExternalFilesPath = SDL_strdup(path);
  1393         env->ReleaseStringUTFChars(pathString, path);
  1394     }
  1395     return s_AndroidExternalFilesPath;
  1396 }
  1397 
  1398 #endif /* __ANDROID__ */
  1399 
  1400 /* vi: set ts=4 sw=4 expandtab: */