src/core/android/SDL_android.c
author Gabriel Jacobo <gabomdq@gmail.com>
Tue, 19 Nov 2013 10:00:05 -0300
changeset 8013 109299fe73ad
parent 7927 4d984f2ea5bb
child 8029 cf81e6709b3d
permissions -rw-r--r--
[Android] Try to improve handling of DPAD|GAMEPAD + KEYBOARD devices

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