src/core/android/SDL_android.c
author Gabriel Jacobo <gabomdq@gmail.com>
Tue, 05 Nov 2013 20:07:39 -0300
changeset 7907 24b4e98c6010
parent 7863 c6b33210e610
child 7910 261addaa47d0
permissions -rw-r--r--
Adds Joystick support for Android

This bumps the build SDK level to 12 (up from 10). Runtime requirements remain
the same (at API level < 12 joystick support is disabled).

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