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