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