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