src/core/android/SDL_android.c
author Sam Lantinga <slouken@libsdl.org>
Sat, 21 Jun 2014 20:35:36 -0700
changeset 8900 537445b25e4e
parent 8776 29aac8b813d9
parent 8897 fff9c0b9f1fb
child 8961 7dbbee37826b
permissions -rw-r--r--
Merged changes from Alexey Petruchik to support Android obb files
http://developer.android.com/google/play/expansion-files.html
     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_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 JNIEXPORT jint JNICALL 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 JNIEXPORT void JNICALL 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 JNIEXPORT void JNICALL 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 JNIEXPORT int JNICALL 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 JNIEXPORT int JNICALL 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 JNIEXPORT void JNICALL 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 JNIEXPORT void JNICALL 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 JNIEXPORT int JNICALL 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 JNIEXPORT int JNICALL 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 JNIEXPORT void JNICALL 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 JNIEXPORT void JNICALL 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 JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_nativeFlipBuffers(JNIEnv* env, jclass jcls)
   260 {
   261     SDL_GL_SwapWindow(Android_Window);
   262 }
   263 
   264 /* Keydown */
   265 JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_onNativeKeyDown(
   266                                     JNIEnv* env, jclass jcls, jint keycode)
   267 {
   268     Android_OnKeyDown(keycode);
   269 }
   270 
   271 /* Keyup */
   272 JNIEXPORT void JNICALL 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 JNIEXPORT void JNICALL 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 JNIEXPORT void JNICALL 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 JNIEXPORT void JNICALL 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 JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_nativeLowMemory(
   309                                     JNIEnv* env, jclass cls)
   310 {
   311     SDL_SendAppEvent(SDL_APP_LOWMEMORY);
   312 }
   313 
   314 /* Quit */
   315 JNIEXPORT void JNICALL 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 JNIEXPORT void JNICALL 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 JNIEXPORT void JNICALL 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 JNIEXPORT void JNICALL 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 JNIEXPORT void JNICALL 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 jstring Java_org_libsdl_app_SDLActivity_nativeGetHint(JNIEnv* env, jclass cls, jstring name) {
   389     const char *utfname = (*env)->GetStringUTFChars(env, name, NULL);
   390     const char *hint = SDL_GetHint(utfname);
   391 
   392     jstring result = (*env)->NewStringUTF(env, hint);
   393     (*env)->ReleaseStringUTFChars(env, name, utfname);
   394 
   395     return result;
   396 }
   397 
   398 /*******************************************************************************
   399              Functions called by SDL into Java
   400 *******************************************************************************/
   401 
   402 static int s_active = 0;
   403 struct LocalReferenceHolder
   404 {
   405     JNIEnv *m_env;
   406     const char *m_func;
   407 };
   408 
   409 static struct LocalReferenceHolder LocalReferenceHolder_Setup(const char *func)
   410 {
   411     struct LocalReferenceHolder refholder;
   412     refholder.m_env = NULL;
   413     refholder.m_func = func;
   414 #ifdef DEBUG_JNI
   415     SDL_Log("Entering function %s", func);
   416 #endif
   417     return refholder;
   418 }
   419 
   420 static SDL_bool LocalReferenceHolder_Init(struct LocalReferenceHolder *refholder, JNIEnv *env)
   421 {
   422     const int capacity = 16;
   423     if ((*env)->PushLocalFrame(env, capacity) < 0) {
   424         SDL_SetError("Failed to allocate enough JVM local references");
   425         return SDL_FALSE;
   426     }
   427     ++s_active;
   428     refholder->m_env = env;
   429     return SDL_TRUE;
   430 }
   431 
   432 static void LocalReferenceHolder_Cleanup(struct LocalReferenceHolder *refholder)
   433 {
   434 #ifdef DEBUG_JNI
   435     SDL_Log("Leaving function %s", refholder->m_func);
   436 #endif
   437     if (refholder->m_env) {
   438         JNIEnv* env = refholder->m_env;
   439         (*env)->PopLocalFrame(env, NULL);
   440         --s_active;
   441     }
   442 }
   443 
   444 static SDL_bool LocalReferenceHolder_IsActive()
   445 {
   446     return s_active > 0;    
   447 }
   448 
   449 ANativeWindow* Android_JNI_GetNativeWindow(void)
   450 {
   451     ANativeWindow* anw;
   452     jobject s;
   453     JNIEnv *env = Android_JNI_GetEnv();
   454 
   455     s = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetNativeSurface);
   456     anw = ANativeWindow_fromSurface(env, s);
   457     (*env)->DeleteLocalRef(env, s);
   458   
   459     return anw;
   460 }
   461 
   462 void Android_JNI_SwapWindow()
   463 {
   464     JNIEnv *mEnv = Android_JNI_GetEnv();
   465     (*mEnv)->CallStaticVoidMethod(mEnv, mActivityClass, midFlipBuffers);
   466 }
   467 
   468 void Android_JNI_SetActivityTitle(const char *title)
   469 {
   470     jmethodID mid;
   471     JNIEnv *mEnv = Android_JNI_GetEnv();
   472     mid = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,"setActivityTitle","(Ljava/lang/String;)Z");
   473     if (mid) {
   474         jstring jtitle = (jstring)((*mEnv)->NewStringUTF(mEnv, title));
   475         (*mEnv)->CallStaticBooleanMethod(mEnv, mActivityClass, mid, jtitle);
   476         (*mEnv)->DeleteLocalRef(mEnv, jtitle);
   477     }
   478 }
   479 
   480 SDL_bool Android_JNI_GetAccelerometerValues(float values[3])
   481 {
   482     int i;
   483     SDL_bool retval = SDL_FALSE;
   484 
   485     if (bHasNewData) {
   486         for (i = 0; i < 3; ++i) {
   487             values[i] = fLastAccelerometer[i];
   488         }
   489         bHasNewData = false;
   490         retval = SDL_TRUE;
   491     }
   492 
   493     return retval;
   494 }
   495 
   496 static void Android_JNI_ThreadDestroyed(void* value)
   497 {
   498     /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */
   499     JNIEnv *env = (JNIEnv*) value;
   500     if (env != NULL) {
   501         (*mJavaVM)->DetachCurrentThread(mJavaVM);
   502         pthread_setspecific(mThreadKey, NULL);
   503     }
   504 }
   505 
   506 JNIEnv* Android_JNI_GetEnv(void)
   507 {
   508     /* From http://developer.android.com/guide/practices/jni.html
   509      * All threads are Linux threads, scheduled by the kernel.
   510      * They're usually started from managed code (using Thread.start), but they can also be created elsewhere and then
   511      * attached to the JavaVM. For example, a thread started with pthread_create can be attached with the
   512      * JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a thread is attached, it has no JNIEnv,
   513      * and cannot make JNI calls.
   514      * Attaching a natively-created thread causes a java.lang.Thread object to be constructed and added to the "main"
   515      * ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread on an already-attached thread
   516      * is a no-op.
   517      * Note: You can call this function any number of times for the same thread, there's no harm in it
   518      */
   519 
   520     JNIEnv *env;
   521     int status = (*mJavaVM)->AttachCurrentThread(mJavaVM, &env, NULL);
   522     if(status < 0) {
   523         LOGE("failed to attach current thread");
   524         return 0;
   525     }
   526 
   527     /* From http://developer.android.com/guide/practices/jni.html
   528      * Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward,
   529      * in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be
   530      * called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific
   531      * to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.)
   532      * Note: The destructor is not called unless the stored value is != NULL
   533      * Note: You can call this function any number of times for the same thread, there's no harm in it
   534      *       (except for some lost CPU cycles)
   535      */
   536     pthread_setspecific(mThreadKey, (void*) env);
   537 
   538     return env;
   539 }
   540 
   541 int Android_JNI_SetupThread(void)
   542 {
   543     Android_JNI_GetEnv();
   544     return 1;
   545 }
   546 
   547 /*
   548  * Audio support
   549  */
   550 static jboolean audioBuffer16Bit = JNI_FALSE;
   551 static jboolean audioBufferStereo = JNI_FALSE;
   552 static jobject audioBuffer = NULL;
   553 static void* audioBufferPinned = NULL;
   554 
   555 int Android_JNI_OpenAudioDevice(int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
   556 {
   557     int audioBufferFrames;
   558 
   559     JNIEnv *env = Android_JNI_GetEnv();
   560 
   561     if (!env) {
   562         LOGE("callback_handler: failed to attach current thread");
   563     }
   564     Android_JNI_SetupThread();
   565 
   566     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device");
   567     audioBuffer16Bit = is16Bit;
   568     audioBufferStereo = channelCount > 1;
   569 
   570     if ((*env)->CallStaticIntMethod(env, mActivityClass, midAudioInit, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames) != 0) {
   571         /* Error during audio initialization */
   572         __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: error on AudioTrack initialization!");
   573         return 0;
   574     }
   575 
   576     /* Allocating the audio buffer from the Java side and passing it as the return value for audioInit no longer works on
   577      * Android >= 4.2 due to a "stale global reference" error. So now we allocate this buffer directly from this side. */
   578 
   579     if (is16Bit) {
   580         jshortArray audioBufferLocal = (*env)->NewShortArray(env, desiredBufferFrames * (audioBufferStereo ? 2 : 1));
   581         if (audioBufferLocal) {
   582             audioBuffer = (*env)->NewGlobalRef(env, audioBufferLocal);
   583             (*env)->DeleteLocalRef(env, audioBufferLocal);
   584         }
   585     }
   586     else {
   587         jbyteArray audioBufferLocal = (*env)->NewByteArray(env, desiredBufferFrames * (audioBufferStereo ? 2 : 1));
   588         if (audioBufferLocal) {
   589             audioBuffer = (*env)->NewGlobalRef(env, audioBufferLocal);
   590             (*env)->DeleteLocalRef(env, audioBufferLocal);
   591         }
   592     }
   593 
   594     if (audioBuffer == NULL) {
   595         __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: could not allocate an audio buffer!");
   596         return 0;
   597     }
   598 
   599     jboolean isCopy = JNI_FALSE;
   600     if (audioBuffer16Bit) {
   601         audioBufferPinned = (*env)->GetShortArrayElements(env, (jshortArray)audioBuffer, &isCopy);
   602         audioBufferFrames = (*env)->GetArrayLength(env, (jshortArray)audioBuffer);
   603     } else {
   604         audioBufferPinned = (*env)->GetByteArrayElements(env, (jbyteArray)audioBuffer, &isCopy);
   605         audioBufferFrames = (*env)->GetArrayLength(env, (jbyteArray)audioBuffer);
   606     }
   607     if (audioBufferStereo) {
   608         audioBufferFrames /= 2;
   609     }
   610 
   611     return audioBufferFrames;
   612 }
   613 
   614 void * Android_JNI_GetAudioBuffer()
   615 {
   616     return audioBufferPinned;
   617 }
   618 
   619 void Android_JNI_WriteAudioBuffer()
   620 {
   621     JNIEnv *mAudioEnv = Android_JNI_GetEnv();
   622 
   623     if (audioBuffer16Bit) {
   624         (*mAudioEnv)->ReleaseShortArrayElements(mAudioEnv, (jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
   625         (*mAudioEnv)->CallStaticVoidMethod(mAudioEnv, mActivityClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
   626     } else {
   627         (*mAudioEnv)->ReleaseByteArrayElements(mAudioEnv, (jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT);
   628         (*mAudioEnv)->CallStaticVoidMethod(mAudioEnv, mActivityClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer);
   629     }
   630 
   631     /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
   632 }
   633 
   634 void Android_JNI_CloseAudioDevice()
   635 {
   636     JNIEnv *env = Android_JNI_GetEnv();
   637 
   638     (*env)->CallStaticVoidMethod(env, mActivityClass, midAudioQuit);
   639 
   640     if (audioBuffer) {
   641         (*env)->DeleteGlobalRef(env, audioBuffer);
   642         audioBuffer = NULL;
   643         audioBufferPinned = NULL;
   644     }
   645 }
   646 
   647 /* Test for an exception and call SDL_SetError with its detail if one occurs */
   648 /* If the parameter silent is truthy then SDL_SetError() will not be called. */
   649 static bool Android_JNI_ExceptionOccurred(bool silent)
   650 {
   651     SDL_assert(LocalReferenceHolder_IsActive());
   652     JNIEnv *mEnv = Android_JNI_GetEnv();
   653 
   654     jthrowable exception = (*mEnv)->ExceptionOccurred(mEnv);
   655     if (exception != NULL) {
   656         jmethodID mid;
   657 
   658         /* Until this happens most JNI operations have undefined behaviour */
   659         (*mEnv)->ExceptionClear(mEnv);
   660 
   661         if (!silent) {
   662             jclass exceptionClass = (*mEnv)->GetObjectClass(mEnv, exception);
   663             jclass classClass = (*mEnv)->FindClass(mEnv, "java/lang/Class");
   664 
   665             mid = (*mEnv)->GetMethodID(mEnv, classClass, "getName", "()Ljava/lang/String;");
   666             jstring exceptionName = (jstring)(*mEnv)->CallObjectMethod(mEnv, exceptionClass, mid);
   667             const char* exceptionNameUTF8 = (*mEnv)->GetStringUTFChars(mEnv, exceptionName, 0);
   668 
   669             mid = (*mEnv)->GetMethodID(mEnv, exceptionClass, "getMessage", "()Ljava/lang/String;");
   670             jstring exceptionMessage = (jstring)(*mEnv)->CallObjectMethod(mEnv, exception, mid);
   671 
   672             if (exceptionMessage != NULL) {
   673                 const char* exceptionMessageUTF8 = (*mEnv)->GetStringUTFChars(mEnv, exceptionMessage, 0);
   674                 SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
   675                 (*mEnv)->ReleaseStringUTFChars(mEnv, exceptionMessage, exceptionMessageUTF8);
   676             } else {
   677                 SDL_SetError("%s", exceptionNameUTF8);
   678             }
   679 
   680             (*mEnv)->ReleaseStringUTFChars(mEnv, exceptionName, exceptionNameUTF8);
   681         }
   682 
   683         return true;
   684     }
   685 
   686     return false;
   687 }
   688 
   689 static int Internal_Android_JNI_FileOpen(SDL_RWops* ctx)
   690 {
   691     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
   692 
   693     int result = 0;
   694 
   695     jmethodID mid;
   696     jobject context;
   697     jobject assetManager;
   698     jobject inputStream;
   699     jclass channels;
   700     jobject readableByteChannel;
   701     jstring fileNameJString;
   702     jobject fd;
   703     jclass fdCls;
   704     jfieldID descriptor;
   705 
   706     JNIEnv *mEnv = Android_JNI_GetEnv();
   707     if (!LocalReferenceHolder_Init(&refs, mEnv)) {
   708         goto failure;
   709     }
   710 
   711     fileNameJString = (jstring)ctx->hidden.androidio.fileNameRef;
   712     ctx->hidden.androidio.position = 0;
   713 
   714     /* context = SDLActivity.getContext(); */
   715     mid = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
   716             "getContext","()Landroid/content/Context;");
   717     context = (*mEnv)->CallStaticObjectMethod(mEnv, mActivityClass, mid);
   718 
   719 
   720     /* assetManager = context.getAssets(); */
   721     mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, context),
   722             "getAssets", "()Landroid/content/res/AssetManager;");
   723     assetManager = (*mEnv)->CallObjectMethod(mEnv, context, mid);
   724 
   725     /* First let's try opening the file to obtain an AssetFileDescriptor.
   726     * This method reads the files directly from the APKs using standard *nix calls
   727     */
   728     mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, assetManager), "openFd", "(Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;");
   729     inputStream = (*mEnv)->CallObjectMethod(mEnv, assetManager, mid, fileNameJString);
   730     if (Android_JNI_ExceptionOccurred(true)) {
   731         goto fallback;
   732     }
   733 
   734     mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getStartOffset", "()J");
   735     ctx->hidden.androidio.offset = (*mEnv)->CallLongMethod(mEnv, inputStream, mid);
   736     if (Android_JNI_ExceptionOccurred(true)) {
   737         goto fallback;
   738     }
   739 
   740     mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getDeclaredLength", "()J");
   741     ctx->hidden.androidio.size = (*mEnv)->CallLongMethod(mEnv, inputStream, mid);
   742     if (Android_JNI_ExceptionOccurred(true)) {
   743         goto fallback;
   744     }
   745 
   746     mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getFileDescriptor", "()Ljava/io/FileDescriptor;");
   747     fd = (*mEnv)->CallObjectMethod(mEnv, inputStream, mid);
   748     fdCls = (*mEnv)->GetObjectClass(mEnv, fd);
   749     descriptor = (*mEnv)->GetFieldID(mEnv, fdCls, "descriptor", "I");
   750     ctx->hidden.androidio.fd = (*mEnv)->GetIntField(mEnv, fd, descriptor);
   751     ctx->hidden.androidio.assetFileDescriptorRef = (*mEnv)->NewGlobalRef(mEnv, inputStream);
   752 
   753     /* Seek to the correct offset in the file. */
   754     lseek(ctx->hidden.androidio.fd, (off_t)ctx->hidden.androidio.offset, SEEK_SET);
   755 
   756     if (false) {
   757 fallback:
   758         /* Disabled log message because of spam on the Nexus 7 */
   759         /* __android_log_print(ANDROID_LOG_DEBUG, "SDL", "Falling back to legacy InputStream method for opening file"); */
   760 
   761         /* Try the old method using InputStream */
   762         ctx->hidden.androidio.assetFileDescriptorRef = NULL;
   763 
   764         /* inputStream = assetManager.open(<filename>); */
   765         mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, assetManager),
   766                 "open", "(Ljava/lang/String;I)Ljava/io/InputStream;");
   767         inputStream = (*mEnv)->CallObjectMethod(mEnv, assetManager, mid, fileNameJString, 1 /* ACCESS_RANDOM */);
   768         if (Android_JNI_ExceptionOccurred(false)) {
   769             // Try fallback to APK Extension files
   770             mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, context),
   771                 "openAPKExtensionInputStream", "(Ljava/lang/String;)Ljava/io/InputStream;");
   772             inputStream = (*mEnv)->CallObjectMethod(mEnv, context, mid, fileNameJString);
   773 
   774             if (Android_JNI_ExceptionOccurred(false)) {
   775                 goto failure;
   776             }
   777         }
   778 
   779         ctx->hidden.androidio.inputStreamRef = (*mEnv)->NewGlobalRef(mEnv, inputStream);
   780 
   781         /* Despite all the visible documentation on [Asset]InputStream claiming
   782          * that the .available() method is not guaranteed to return the entire file
   783          * size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
   784          * android/apis/content/ReadAsset.java imply that Android's
   785          * AssetInputStream.available() /will/ always return the total file size
   786         */
   787         
   788         /* size = inputStream.available(); */
   789         mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream),
   790                 "available", "()I");
   791         ctx->hidden.androidio.size = (long)(*mEnv)->CallIntMethod(mEnv, inputStream, mid);
   792         if (Android_JNI_ExceptionOccurred(false)) {
   793             goto failure;
   794         }
   795 
   796         /* readableByteChannel = Channels.newChannel(inputStream); */
   797         channels = (*mEnv)->FindClass(mEnv, "java/nio/channels/Channels");
   798         mid = (*mEnv)->GetStaticMethodID(mEnv, channels,
   799                 "newChannel",
   800                 "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
   801         readableByteChannel = (*mEnv)->CallStaticObjectMethod(
   802                 mEnv, channels, mid, inputStream);
   803         if (Android_JNI_ExceptionOccurred(false)) {
   804             goto failure;
   805         }
   806 
   807         ctx->hidden.androidio.readableByteChannelRef =
   808             (*mEnv)->NewGlobalRef(mEnv, readableByteChannel);
   809 
   810         /* Store .read id for reading purposes */
   811         mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, readableByteChannel),
   812                 "read", "(Ljava/nio/ByteBuffer;)I");
   813         ctx->hidden.androidio.readMethod = mid;
   814     }
   815 
   816     if (false) {
   817 failure:
   818         result = -1;
   819 
   820         (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.fileNameRef);
   821 
   822         if(ctx->hidden.androidio.inputStreamRef != NULL) {
   823             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.inputStreamRef);
   824         }
   825 
   826         if(ctx->hidden.androidio.readableByteChannelRef != NULL) {
   827             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.readableByteChannelRef);
   828         }
   829 
   830         if(ctx->hidden.androidio.assetFileDescriptorRef != NULL) {
   831             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.assetFileDescriptorRef);
   832         }
   833 
   834     }
   835     
   836     LocalReferenceHolder_Cleanup(&refs);
   837     return result;
   838 }
   839 
   840 int Android_JNI_FileOpen(SDL_RWops* ctx,
   841         const char* fileName, const char* mode)
   842 {
   843     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
   844     JNIEnv *mEnv = Android_JNI_GetEnv();
   845     int retval;
   846 
   847     if (!LocalReferenceHolder_Init(&refs, mEnv)) {
   848         LocalReferenceHolder_Cleanup(&refs);        
   849         return -1;
   850     }
   851 
   852     if (!ctx) {
   853         LocalReferenceHolder_Cleanup(&refs);
   854         return -1;
   855     }
   856 
   857     jstring fileNameJString = (*mEnv)->NewStringUTF(mEnv, fileName);
   858     ctx->hidden.androidio.fileNameRef = (*mEnv)->NewGlobalRef(mEnv, fileNameJString);
   859     ctx->hidden.androidio.inputStreamRef = NULL;
   860     ctx->hidden.androidio.readableByteChannelRef = NULL;
   861     ctx->hidden.androidio.readMethod = NULL;
   862     ctx->hidden.androidio.assetFileDescriptorRef = NULL;
   863 
   864     retval = Internal_Android_JNI_FileOpen(ctx);
   865     LocalReferenceHolder_Cleanup(&refs);
   866     return retval;
   867 }
   868 
   869 size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
   870         size_t size, size_t maxnum)
   871 {
   872     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
   873 
   874     if (ctx->hidden.androidio.assetFileDescriptorRef) {
   875         size_t bytesMax = size * maxnum;
   876         if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && ctx->hidden.androidio.position + bytesMax > ctx->hidden.androidio.size) {
   877             bytesMax = ctx->hidden.androidio.size - ctx->hidden.androidio.position;
   878         }
   879         size_t result = read(ctx->hidden.androidio.fd, buffer, bytesMax );
   880         if (result > 0) {
   881             ctx->hidden.androidio.position += result;
   882             LocalReferenceHolder_Cleanup(&refs);
   883             return result / size;
   884         }
   885         LocalReferenceHolder_Cleanup(&refs);
   886         return 0;
   887     } else {
   888         jlong bytesRemaining = (jlong) (size * maxnum);
   889         jlong bytesMax = (jlong) (ctx->hidden.androidio.size -  ctx->hidden.androidio.position);
   890         int bytesRead = 0;
   891 
   892         /* Don't read more bytes than those that remain in the file, otherwise we get an exception */
   893         if (bytesRemaining >  bytesMax) bytesRemaining = bytesMax;
   894 
   895         JNIEnv *mEnv = Android_JNI_GetEnv();
   896         if (!LocalReferenceHolder_Init(&refs, mEnv)) {
   897             LocalReferenceHolder_Cleanup(&refs);            
   898             return 0;
   899         }
   900 
   901         jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannelRef;
   902         jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
   903         jobject byteBuffer = (*mEnv)->NewDirectByteBuffer(mEnv, buffer, bytesRemaining);
   904 
   905         while (bytesRemaining > 0) {
   906             /* result = readableByteChannel.read(...); */
   907             int result = (*mEnv)->CallIntMethod(mEnv, readableByteChannel, readMethod, byteBuffer);
   908 
   909             if (Android_JNI_ExceptionOccurred(false)) {
   910                 LocalReferenceHolder_Cleanup(&refs);            
   911                 return 0;
   912             }
   913 
   914             if (result < 0) {
   915                 break;
   916             }
   917 
   918             bytesRemaining -= result;
   919             bytesRead += result;
   920             ctx->hidden.androidio.position += result;
   921         }
   922         LocalReferenceHolder_Cleanup(&refs);                    
   923         return bytesRead / size;
   924     }
   925 }
   926 
   927 size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
   928         size_t size, size_t num)
   929 {
   930     SDL_SetError("Cannot write to Android package filesystem");
   931     return 0;
   932 }
   933 
   934 static int Internal_Android_JNI_FileClose(SDL_RWops* ctx, bool release)
   935 {
   936     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
   937 
   938     int result = 0;
   939     JNIEnv *mEnv = Android_JNI_GetEnv();
   940 
   941     if (!LocalReferenceHolder_Init(&refs, mEnv)) {
   942         LocalReferenceHolder_Cleanup(&refs);
   943         return SDL_SetError("Failed to allocate enough JVM local references");
   944     }
   945 
   946     if (ctx) {
   947         if (release) {
   948             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.fileNameRef);
   949         }
   950 
   951         if (ctx->hidden.androidio.assetFileDescriptorRef) {
   952             jobject inputStream = (jobject)ctx->hidden.androidio.assetFileDescriptorRef;
   953             jmethodID mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream),
   954                     "close", "()V");
   955             (*mEnv)->CallVoidMethod(mEnv, inputStream, mid);
   956             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.assetFileDescriptorRef);
   957             if (Android_JNI_ExceptionOccurred(false)) {
   958                 result = -1;
   959             }
   960         }
   961         else {
   962             jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
   963 
   964             /* inputStream.close(); */
   965             jmethodID mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream),
   966                     "close", "()V");
   967             (*mEnv)->CallVoidMethod(mEnv, inputStream, mid);
   968             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.inputStreamRef);
   969             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.readableByteChannelRef);
   970             if (Android_JNI_ExceptionOccurred(false)) {
   971                 result = -1;
   972             }
   973         }
   974 
   975         if (release) {
   976             SDL_FreeRW(ctx);
   977         }
   978     }
   979 
   980     LocalReferenceHolder_Cleanup(&refs);
   981     return result;
   982 }
   983 
   984 
   985 Sint64 Android_JNI_FileSize(SDL_RWops* ctx)
   986 {
   987     return ctx->hidden.androidio.size;
   988 }
   989 
   990 Sint64 Android_JNI_FileSeek(SDL_RWops* ctx, Sint64 offset, int whence)
   991 {
   992     if (ctx->hidden.androidio.assetFileDescriptorRef) {
   993         switch (whence) {
   994             case RW_SEEK_SET:
   995                 if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
   996                 offset += ctx->hidden.androidio.offset;
   997                 break;
   998             case RW_SEEK_CUR:
   999                 offset += ctx->hidden.androidio.position;
  1000                 if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
  1001                 offset += ctx->hidden.androidio.offset;
  1002                 break;
  1003             case RW_SEEK_END:
  1004                 offset = ctx->hidden.androidio.offset + ctx->hidden.androidio.size + offset;
  1005                 break;
  1006             default:
  1007                 return SDL_SetError("Unknown value for 'whence'");
  1008         }
  1009         whence = SEEK_SET;
  1010 
  1011         off_t ret = lseek(ctx->hidden.androidio.fd, (off_t)offset, SEEK_SET);
  1012         if (ret == -1) return -1;
  1013         ctx->hidden.androidio.position = ret - ctx->hidden.androidio.offset;
  1014     } else {
  1015         Sint64 newPosition;
  1016 
  1017         switch (whence) {
  1018             case RW_SEEK_SET:
  1019                 newPosition = offset;
  1020                 break;
  1021             case RW_SEEK_CUR:
  1022                 newPosition = ctx->hidden.androidio.position + offset;
  1023                 break;
  1024             case RW_SEEK_END:
  1025                 newPosition = ctx->hidden.androidio.size + offset;
  1026                 break;
  1027             default:
  1028                 return SDL_SetError("Unknown value for 'whence'");
  1029         }
  1030 
  1031         /* Validate the new position */
  1032         if (newPosition < 0) {
  1033             return SDL_Error(SDL_EFSEEK);
  1034         }
  1035         if (newPosition > ctx->hidden.androidio.size) {
  1036             newPosition = ctx->hidden.androidio.size;
  1037         }
  1038 
  1039         Sint64 movement = newPosition - ctx->hidden.androidio.position;
  1040         if (movement > 0) {
  1041             unsigned char buffer[4096];
  1042 
  1043             /* The easy case where we're seeking forwards */
  1044             while (movement > 0) {
  1045                 Sint64 amount = sizeof (buffer);
  1046                 if (amount > movement) {
  1047                     amount = movement;
  1048                 }
  1049                 size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount);
  1050                 if (result <= 0) {
  1051                     /* Failed to read/skip the required amount, so fail */
  1052                     return -1;
  1053                 }
  1054 
  1055                 movement -= result;
  1056             }
  1057 
  1058         } else if (movement < 0) {
  1059             /* We can't seek backwards so we have to reopen the file and seek */
  1060             /* forwards which obviously isn't very efficient */
  1061             Internal_Android_JNI_FileClose(ctx, false);
  1062             Internal_Android_JNI_FileOpen(ctx);
  1063             Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
  1064         }
  1065     }
  1066 
  1067     return ctx->hidden.androidio.position;
  1068 
  1069 }
  1070 
  1071 int Android_JNI_FileClose(SDL_RWops* ctx)
  1072 {
  1073     return Internal_Android_JNI_FileClose(ctx, true);
  1074 }
  1075 
  1076 /* returns a new global reference which needs to be released later */
  1077 static jobject Android_JNI_GetSystemServiceObject(const char* name)
  1078 {
  1079     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  1080     JNIEnv* env = Android_JNI_GetEnv();
  1081     jobject retval = NULL;
  1082 
  1083     if (!LocalReferenceHolder_Init(&refs, env)) {
  1084         LocalReferenceHolder_Cleanup(&refs);
  1085         return NULL;
  1086     }
  1087 
  1088     jstring service = (*env)->NewStringUTF(env, name);
  1089 
  1090     jmethodID mid;
  1091 
  1092     mid = (*env)->GetStaticMethodID(env, mActivityClass, "getContext", "()Landroid/content/Context;");
  1093     jobject context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
  1094 
  1095     mid = (*env)->GetMethodID(env, mActivityClass, "getSystemServiceFromUiThread", "(Ljava/lang/String;)Ljava/lang/Object;");
  1096     jobject manager = (*env)->CallObjectMethod(env, context, mid, service);
  1097 
  1098     (*env)->DeleteLocalRef(env, service);
  1099 
  1100     retval = manager ? (*env)->NewGlobalRef(env, manager) : NULL;
  1101     LocalReferenceHolder_Cleanup(&refs);
  1102     return retval;
  1103 }
  1104 
  1105 #define SETUP_CLIPBOARD(error) \
  1106     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); \
  1107     JNIEnv* env = Android_JNI_GetEnv(); \
  1108     if (!LocalReferenceHolder_Init(&refs, env)) { \
  1109         LocalReferenceHolder_Cleanup(&refs); \
  1110         return error; \
  1111     } \
  1112     jobject clipboard = Android_JNI_GetSystemServiceObject("clipboard"); \
  1113     if (!clipboard) { \
  1114         LocalReferenceHolder_Cleanup(&refs); \
  1115         return error; \
  1116     }
  1117 
  1118 #define CLEANUP_CLIPBOARD() \
  1119     LocalReferenceHolder_Cleanup(&refs);
  1120 
  1121 int Android_JNI_SetClipboardText(const char* text)
  1122 {
  1123     SETUP_CLIPBOARD(-1)
  1124 
  1125     jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, clipboard), "setText", "(Ljava/lang/CharSequence;)V");
  1126     jstring string = (*env)->NewStringUTF(env, text);
  1127     (*env)->CallVoidMethod(env, clipboard, mid, string);
  1128     (*env)->DeleteGlobalRef(env, clipboard);
  1129     (*env)->DeleteLocalRef(env, string);
  1130 
  1131     CLEANUP_CLIPBOARD();
  1132 
  1133     return 0;
  1134 }
  1135 
  1136 char* Android_JNI_GetClipboardText()
  1137 {
  1138     SETUP_CLIPBOARD(SDL_strdup(""))
  1139 
  1140     jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, clipboard), "getText", "()Ljava/lang/CharSequence;");
  1141     jobject sequence = (*env)->CallObjectMethod(env, clipboard, mid);
  1142     (*env)->DeleteGlobalRef(env, clipboard);
  1143     if (sequence) {
  1144         mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, sequence), "toString", "()Ljava/lang/String;");
  1145         jstring string = (jstring)((*env)->CallObjectMethod(env, sequence, mid));
  1146         const char* utf = (*env)->GetStringUTFChars(env, string, 0);
  1147         if (utf) {
  1148             char* text = SDL_strdup(utf);
  1149             (*env)->ReleaseStringUTFChars(env, string, utf);
  1150 
  1151             CLEANUP_CLIPBOARD();
  1152 
  1153             return text;
  1154         }
  1155     }
  1156 
  1157     CLEANUP_CLIPBOARD();    
  1158 
  1159     return SDL_strdup("");
  1160 }
  1161 
  1162 SDL_bool Android_JNI_HasClipboardText()
  1163 {
  1164     SETUP_CLIPBOARD(SDL_FALSE)
  1165 
  1166     jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, clipboard), "hasText", "()Z");
  1167     jboolean has = (*env)->CallBooleanMethod(env, clipboard, mid);
  1168     (*env)->DeleteGlobalRef(env, clipboard);
  1169 
  1170     CLEANUP_CLIPBOARD();
  1171     
  1172     return has ? SDL_TRUE : SDL_FALSE;
  1173 }
  1174 
  1175 
  1176 /* returns 0 on success or -1 on error (others undefined then)
  1177  * returns truthy or falsy value in plugged, charged and battery
  1178  * returns the value in seconds and percent or -1 if not available
  1179  */
  1180 int Android_JNI_GetPowerInfo(int* plugged, int* charged, int* battery, int* seconds, int* percent)
  1181 {
  1182     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  1183     JNIEnv* env = Android_JNI_GetEnv();
  1184     if (!LocalReferenceHolder_Init(&refs, env)) {
  1185         LocalReferenceHolder_Cleanup(&refs);
  1186         return -1;
  1187     }
  1188 
  1189     jmethodID mid;
  1190 
  1191     mid = (*env)->GetStaticMethodID(env, mActivityClass, "getContext", "()Landroid/content/Context;");
  1192     jobject context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
  1193 
  1194     jstring action = (*env)->NewStringUTF(env, "android.intent.action.BATTERY_CHANGED");
  1195 
  1196     jclass cls = (*env)->FindClass(env, "android/content/IntentFilter");
  1197 
  1198     mid = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/lang/String;)V");
  1199     jobject filter = (*env)->NewObject(env, cls, mid, action);
  1200 
  1201     (*env)->DeleteLocalRef(env, action);
  1202 
  1203     mid = (*env)->GetMethodID(env, mActivityClass, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;");
  1204     jobject intent = (*env)->CallObjectMethod(env, context, mid, NULL, filter);
  1205 
  1206     (*env)->DeleteLocalRef(env, filter);
  1207 
  1208     cls = (*env)->GetObjectClass(env, intent);
  1209 
  1210     jstring iname;
  1211     jmethodID imid = (*env)->GetMethodID(env, cls, "getIntExtra", "(Ljava/lang/String;I)I");
  1212 
  1213 #define GET_INT_EXTRA(var, key) \
  1214     iname = (*env)->NewStringUTF(env, key); \
  1215     int var = (*env)->CallIntMethod(env, intent, imid, iname, -1); \
  1216     (*env)->DeleteLocalRef(env, iname);
  1217 
  1218     jstring bname;
  1219     jmethodID bmid = (*env)->GetMethodID(env, cls, "getBooleanExtra", "(Ljava/lang/String;Z)Z");
  1220 
  1221 #define GET_BOOL_EXTRA(var, key) \
  1222     bname = (*env)->NewStringUTF(env, key); \
  1223     int var = (*env)->CallBooleanMethod(env, intent, bmid, bname, JNI_FALSE); \
  1224     (*env)->DeleteLocalRef(env, bname);
  1225 
  1226     if (plugged) {
  1227         GET_INT_EXTRA(plug, "plugged") /* == BatteryManager.EXTRA_PLUGGED (API 5) */
  1228         if (plug == -1) {
  1229             LocalReferenceHolder_Cleanup(&refs);
  1230             return -1;
  1231         }
  1232         /* 1 == BatteryManager.BATTERY_PLUGGED_AC */
  1233         /* 2 == BatteryManager.BATTERY_PLUGGED_USB */
  1234         *plugged = (0 < plug) ? 1 : 0;
  1235     }
  1236 
  1237     if (charged) {
  1238         GET_INT_EXTRA(status, "status") /* == BatteryManager.EXTRA_STATUS (API 5) */
  1239         if (status == -1) {
  1240             LocalReferenceHolder_Cleanup(&refs);
  1241             return -1;
  1242         }
  1243         /* 5 == BatteryManager.BATTERY_STATUS_FULL */
  1244         *charged = (status == 5) ? 1 : 0;
  1245     }
  1246 
  1247     if (battery) {
  1248         GET_BOOL_EXTRA(present, "present") /* == BatteryManager.EXTRA_PRESENT (API 5) */
  1249         *battery = present ? 1 : 0;
  1250     }
  1251 
  1252     if (seconds) {
  1253         *seconds = -1; /* not possible */
  1254     }
  1255 
  1256     if (percent) {
  1257         GET_INT_EXTRA(level, "level") /* == BatteryManager.EXTRA_LEVEL (API 5) */
  1258         GET_INT_EXTRA(scale, "scale") /* == BatteryManager.EXTRA_SCALE (API 5) */
  1259         if ((level == -1) || (scale == -1)) {
  1260             LocalReferenceHolder_Cleanup(&refs);
  1261             return -1;
  1262         }
  1263         *percent = level * 100 / scale;
  1264     }
  1265 
  1266     (*env)->DeleteLocalRef(env, intent);
  1267 
  1268     LocalReferenceHolder_Cleanup(&refs);
  1269     return 0;
  1270 }
  1271 
  1272 /* returns number of found touch devices as return value and ids in parameter ids */
  1273 int Android_JNI_GetTouchDeviceIds(int **ids) {
  1274     JNIEnv *env = Android_JNI_GetEnv();
  1275     jint sources = 4098; /* == InputDevice.SOURCE_TOUCHSCREEN */
  1276     jmethodID mid = (*env)->GetStaticMethodID(env, mActivityClass, "inputGetInputDeviceIds", "(I)[I");
  1277     jintArray array = (jintArray) (*env)->CallStaticObjectMethod(env, mActivityClass, mid, sources);
  1278     int number = 0;
  1279     *ids = NULL;
  1280     if (array) {
  1281         number = (int) (*env)->GetArrayLength(env, array);
  1282         if (0 < number) {
  1283             jint* elements = (*env)->GetIntArrayElements(env, array, NULL);
  1284             if (elements) {
  1285                 int i;
  1286                 *ids = SDL_malloc(number * sizeof (**ids));
  1287                 for (i = 0; i < number; ++i) { /* not assuming sizeof (jint) == sizeof (int) */
  1288                     (*ids)[i] = elements[i];
  1289                 }
  1290                 (*env)->ReleaseIntArrayElements(env, array, elements, JNI_ABORT);
  1291             }
  1292         }
  1293         (*env)->DeleteLocalRef(env, array);
  1294     }
  1295     return number;
  1296 }
  1297 
  1298 void Android_JNI_PollInputDevices()
  1299 {
  1300     JNIEnv *env = Android_JNI_GetEnv();
  1301     (*env)->CallStaticVoidMethod(env, mActivityClass, midPollInputDevices);    
  1302 }
  1303 
  1304 /* sends message to be handled on the UI event dispatch thread */
  1305 int Android_JNI_SendMessage(int command, int param)
  1306 {
  1307     JNIEnv *env = Android_JNI_GetEnv();
  1308     if (!env) {
  1309         return -1;
  1310     }
  1311     jmethodID mid = (*env)->GetStaticMethodID(env, mActivityClass, "sendMessage", "(II)Z");
  1312     if (!mid) {
  1313         return -1;
  1314     }
  1315     jboolean success = (*env)->CallStaticBooleanMethod(env, mActivityClass, mid, command, param);
  1316     return success ? 0 : -1;
  1317 }
  1318 
  1319 void Android_JNI_ShowTextInput(SDL_Rect *inputRect)
  1320 {
  1321     JNIEnv *env = Android_JNI_GetEnv();
  1322     if (!env) {
  1323         return;
  1324     }
  1325 
  1326     jmethodID mid = (*env)->GetStaticMethodID(env, mActivityClass, "showTextInput", "(IIII)Z");
  1327     if (!mid) {
  1328         return;
  1329     }
  1330     (*env)->CallStaticBooleanMethod(env, mActivityClass, mid,
  1331                                inputRect->x,
  1332                                inputRect->y,
  1333                                inputRect->w,
  1334                                inputRect->h );
  1335 }
  1336 
  1337 void Android_JNI_HideTextInput()
  1338 {
  1339     /* has to match Activity constant */
  1340     const int COMMAND_TEXTEDIT_HIDE = 3;
  1341     Android_JNI_SendMessage(COMMAND_TEXTEDIT_HIDE, 0);
  1342 }
  1343 
  1344 /*
  1345 //////////////////////////////////////////////////////////////////////////////
  1346 //
  1347 // Functions exposed to SDL applications in SDL_system.h
  1348 //////////////////////////////////////////////////////////////////////////////
  1349 */
  1350 
  1351 void *SDL_AndroidGetJNIEnv()
  1352 {
  1353     return Android_JNI_GetEnv();
  1354 }
  1355 
  1356 
  1357 
  1358 void *SDL_AndroidGetActivity()
  1359 {
  1360     /* See SDL_system.h for caveats on using this function. */
  1361 
  1362     jmethodID mid;
  1363 
  1364     JNIEnv *env = Android_JNI_GetEnv();
  1365     if (!env) {
  1366         return NULL;
  1367     }
  1368 
  1369     /* return SDLActivity.getContext(); */
  1370     mid = (*env)->GetStaticMethodID(env, mActivityClass,
  1371             "getContext","()Landroid/content/Context;");
  1372     return (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
  1373 }
  1374 
  1375 const char * SDL_AndroidGetInternalStoragePath()
  1376 {
  1377     static char *s_AndroidInternalFilesPath = NULL;
  1378 
  1379     if (!s_AndroidInternalFilesPath) {
  1380         struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  1381         jmethodID mid;
  1382         jobject context;
  1383         jobject fileObject;
  1384         jstring pathString;
  1385         const char *path;
  1386 
  1387         JNIEnv *env = Android_JNI_GetEnv();
  1388         if (!LocalReferenceHolder_Init(&refs, env)) {
  1389             LocalReferenceHolder_Cleanup(&refs);
  1390             return NULL;
  1391         }
  1392 
  1393         /* context = SDLActivity.getContext(); */
  1394         mid = (*env)->GetStaticMethodID(env, mActivityClass,
  1395                 "getContext","()Landroid/content/Context;");
  1396         context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
  1397 
  1398         /* fileObj = context.getFilesDir(); */
  1399         mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
  1400                 "getFilesDir", "()Ljava/io/File;");
  1401         fileObject = (*env)->CallObjectMethod(env, context, mid);
  1402         if (!fileObject) {
  1403             SDL_SetError("Couldn't get internal directory");
  1404             LocalReferenceHolder_Cleanup(&refs);
  1405             return NULL;
  1406         }
  1407 
  1408         /* path = fileObject.getAbsolutePath(); */
  1409         mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject),
  1410                 "getAbsolutePath", "()Ljava/lang/String;");
  1411         pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid);
  1412 
  1413         path = (*env)->GetStringUTFChars(env, pathString, NULL);
  1414         s_AndroidInternalFilesPath = SDL_strdup(path);
  1415         (*env)->ReleaseStringUTFChars(env, pathString, path);
  1416 
  1417         LocalReferenceHolder_Cleanup(&refs);
  1418     }
  1419     return s_AndroidInternalFilesPath;
  1420 }
  1421 
  1422 int SDL_AndroidGetExternalStorageState()
  1423 {
  1424     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  1425     jmethodID mid;
  1426     jclass cls;
  1427     jstring stateString;
  1428     const char *state;
  1429     int stateFlags;
  1430 
  1431     JNIEnv *env = Android_JNI_GetEnv();
  1432     if (!LocalReferenceHolder_Init(&refs, env)) {
  1433         LocalReferenceHolder_Cleanup(&refs);
  1434         return 0;
  1435     }
  1436 
  1437     cls = (*env)->FindClass(env, "android/os/Environment");
  1438     mid = (*env)->GetStaticMethodID(env, cls,
  1439             "getExternalStorageState", "()Ljava/lang/String;");
  1440     stateString = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid);
  1441 
  1442     state = (*env)->GetStringUTFChars(env, stateString, NULL);
  1443 
  1444     /* Print an info message so people debugging know the storage state */
  1445     __android_log_print(ANDROID_LOG_INFO, "SDL", "external storage state: %s", state);
  1446 
  1447     if (SDL_strcmp(state, "mounted") == 0) {
  1448         stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ |
  1449                      SDL_ANDROID_EXTERNAL_STORAGE_WRITE;
  1450     } else if (SDL_strcmp(state, "mounted_ro") == 0) {
  1451         stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ;
  1452     } else {
  1453         stateFlags = 0;
  1454     }
  1455     (*env)->ReleaseStringUTFChars(env, stateString, state);
  1456 
  1457     LocalReferenceHolder_Cleanup(&refs);
  1458     return stateFlags;
  1459 }
  1460 
  1461 const char * SDL_AndroidGetExternalStoragePath()
  1462 {
  1463     static char *s_AndroidExternalFilesPath = NULL;
  1464 
  1465     if (!s_AndroidExternalFilesPath) {
  1466         struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  1467         jmethodID mid;
  1468         jobject context;
  1469         jobject fileObject;
  1470         jstring pathString;
  1471         const char *path;
  1472 
  1473         JNIEnv *env = Android_JNI_GetEnv();
  1474         if (!LocalReferenceHolder_Init(&refs, env)) {
  1475             LocalReferenceHolder_Cleanup(&refs);
  1476             return NULL;
  1477         }
  1478 
  1479         /* context = SDLActivity.getContext(); */
  1480         mid = (*env)->GetStaticMethodID(env, mActivityClass,
  1481                 "getContext","()Landroid/content/Context;");
  1482         context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
  1483 
  1484         /* fileObj = context.getExternalFilesDir(); */
  1485         mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
  1486                 "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;");
  1487         fileObject = (*env)->CallObjectMethod(env, context, mid, NULL);
  1488         if (!fileObject) {
  1489             SDL_SetError("Couldn't get external directory");
  1490             LocalReferenceHolder_Cleanup(&refs);
  1491             return NULL;
  1492         }
  1493 
  1494         /* path = fileObject.getAbsolutePath(); */
  1495         mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject),
  1496                 "getAbsolutePath", "()Ljava/lang/String;");
  1497         pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid);
  1498 
  1499         path = (*env)->GetStringUTFChars(env, pathString, NULL);
  1500         s_AndroidExternalFilesPath = SDL_strdup(path);
  1501         (*env)->ReleaseStringUTFChars(env, pathString, path);
  1502 
  1503         LocalReferenceHolder_Cleanup(&refs);
  1504     }
  1505     return s_AndroidExternalFilesPath;
  1506 }
  1507 
  1508 #endif /* __ANDROID__ */
  1509 
  1510 /* vi: set ts=4 sw=4 expandtab: */
  1511