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