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