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