src/core/android/SDL_android.cpp
author Gabriel Jacobo <gabomdq@gmail.com>
Mon, 07 Jan 2013 12:22:26 -0300
changeset 6802 8a6b8ce97656
parent 6792 a15821a3471f
child 6806 9e57ff36fd7a
permissions -rw-r--r--
Fix Audio Buffer allocation on Android >= 4.2
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2012 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 extern "C" {
    33 #include "../../events/SDL_events_c.h"
    34 #include "../../video/android/SDL_androidkeyboard.h"
    35 #include "../../video/android/SDL_androidtouch.h"
    36 #include "../../video/android/SDL_androidvideo.h"
    37 
    38 #include <android/log.h>
    39 #include <pthread.h>
    40 #define LOG_TAG "SDL_android"
    41 //#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
    42 //#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
    43 #define LOGI(...) do {} while (false)
    44 #define LOGE(...) do {} while (false)
    45 
    46 /* Uncomment this to log messages entering and exiting methods in this file */
    47 //#define DEBUG_JNI
    48 
    49 /* Implemented in audio/android/SDL_androidaudio.c */
    50 extern void Android_RunAudioThread();
    51 } // C
    52 
    53 /*******************************************************************************
    54  This file links the Java side of Android with libsdl
    55 *******************************************************************************/
    56 #include <jni.h>
    57 #include <android/log.h>
    58 
    59 
    60 /*******************************************************************************
    61                                Globals
    62 *******************************************************************************/
    63 static pthread_key_t mThreadKey;
    64 static JavaVM* mJavaVM;
    65 
    66 // Main activity
    67 static jclass mActivityClass;
    68 
    69 // method signatures
    70 static jmethodID midCreateGLContext;
    71 static jmethodID midFlipBuffers;
    72 static jmethodID midAudioInit;
    73 static jmethodID midAudioWriteShortBuffer;
    74 static jmethodID midAudioWriteByteBuffer;
    75 static jmethodID midAudioQuit;
    76 
    77 // Accelerometer data storage
    78 static float fLastAccelerometer[3];
    79 static bool bHasNewData;
    80 
    81 /*******************************************************************************
    82                  Functions called by JNI
    83 *******************************************************************************/
    84 
    85 // Library init
    86 extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
    87 {
    88     JNIEnv *env;
    89     mJavaVM = vm;
    90     LOGI("JNI_OnLoad called");
    91     if (mJavaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
    92         LOGE("Failed to get the environment using GetEnv()");
    93         return -1;
    94     }
    95     /*
    96      * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread
    97      * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this
    98      */
    99     if (pthread_key_create(&mThreadKey, Android_JNI_ThreadDestroyed)) {
   100         __android_log_print(ANDROID_LOG_ERROR, "SDL", "Error initializing pthread key");
   101     }
   102     else {
   103         Android_JNI_SetupThread();
   104     }
   105 
   106     return JNI_VERSION_1_4;
   107 }
   108 
   109 // Called before SDL_main() to initialize JNI bindings
   110 extern "C" 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(cls);
   117 
   118     midCreateGLContext = mEnv->GetStaticMethodID(mActivityClass,
   119                                 "createGLContext","(II[I)Z");
   120     midFlipBuffers = mEnv->GetStaticMethodID(mActivityClass,
   121                                 "flipBuffers","()V");
   122     midAudioInit = mEnv->GetStaticMethodID(mActivityClass, 
   123                                 "audioInit", "(IZZI)V");
   124     midAudioWriteShortBuffer = mEnv->GetStaticMethodID(mActivityClass,
   125                                 "audioWriteShortBuffer", "([S)V");
   126     midAudioWriteByteBuffer = mEnv->GetStaticMethodID(mActivityClass,
   127                                 "audioWriteByteBuffer", "([B)V");
   128     midAudioQuit = mEnv->GetStaticMethodID(mActivityClass,
   129                                 "audioQuit", "()V");
   130 
   131     bHasNewData = false;
   132 
   133     if(!midCreateGLContext || !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 extern "C" 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 // Keydown
   149 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyDown(
   150                                     JNIEnv* env, jclass jcls, jint keycode)
   151 {
   152     Android_OnKeyDown(keycode);
   153 }
   154 
   155 // Keyup
   156 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyUp(
   157                                     JNIEnv* env, jclass jcls, jint keycode)
   158 {
   159     Android_OnKeyUp(keycode);
   160 }
   161 
   162 // Touch
   163 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeTouch(
   164                                     JNIEnv* env, jclass jcls,
   165                                     jint touch_device_id_in, jint pointer_finger_id_in,
   166                                     jint action, jfloat x, jfloat y, jfloat p)
   167 {
   168     Android_OnTouch(touch_device_id_in, pointer_finger_id_in, action, x, y, p);
   169 }
   170 
   171 // Accelerometer
   172 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeAccel(
   173                                     JNIEnv* env, jclass jcls,
   174                                     jfloat x, jfloat y, jfloat z)
   175 {
   176     fLastAccelerometer[0] = x;
   177     fLastAccelerometer[1] = y;
   178     fLastAccelerometer[2] = z;
   179     bHasNewData = true;
   180 }
   181 
   182 // Quit
   183 extern "C" void Java_org_libsdl_app_SDLActivity_nativeQuit(
   184                                     JNIEnv* env, jclass cls)
   185 {    
   186     // Inject a SDL_QUIT event
   187     SDL_SendQuit();
   188 }
   189 
   190 // Pause
   191 extern "C" void Java_org_libsdl_app_SDLActivity_nativePause(
   192                                     JNIEnv* env, jclass cls)
   193 {
   194     if (Android_Window) {
   195         /* Signal the pause semaphore so the event loop knows to pause and (optionally) block itself */
   196         if (!SDL_SemValue(Android_PauseSem)) SDL_SemPost(Android_PauseSem);
   197         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_LOST, 0, 0);
   198         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_MINIMIZED, 0, 0);
   199     }
   200 }
   201 
   202 // Resume
   203 extern "C" void Java_org_libsdl_app_SDLActivity_nativeResume(
   204                                     JNIEnv* env, jclass cls)
   205 {
   206     if (Android_Window) {
   207         /* Signal the resume semaphore so the event loop knows to resume and restore the GL Context
   208          * We can't restore the GL Context here because it needs to be done on the SDL main thread
   209          * and this function will be called from the Java thread instead.
   210          */
   211         if (!SDL_SemValue(Android_ResumeSem)) SDL_SemPost(Android_ResumeSem);
   212         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_GAINED, 0, 0);
   213         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_RESTORED, 0, 0);
   214     }
   215 }
   216 
   217 extern "C" void Java_org_libsdl_app_SDLActivity_nativeRunAudioThread(
   218                                     JNIEnv* env, jclass cls)
   219 {
   220     /* This is the audio thread, with a different environment */
   221     Android_JNI_SetupThread();
   222 
   223     Android_RunAudioThread();
   224 }
   225 
   226 extern "C" void Java_org_libsdl_app_SDLInputConnection_nativeCommitText(
   227                                     JNIEnv* env, jclass cls,
   228                                     jstring text, jint newCursorPosition)
   229 {
   230     const char *utftext = env->GetStringUTFChars(text, NULL);
   231 
   232     SDL_SendKeyboardText(utftext);
   233 
   234     env->ReleaseStringUTFChars(text, utftext);
   235 }
   236 
   237 extern "C" void Java_org_libsdl_app_SDLInputConnection_nativeSetComposingText(
   238                                     JNIEnv* env, jclass cls,
   239                                     jstring text, jint newCursorPosition)
   240 {
   241     const char *utftext = env->GetStringUTFChars(text, NULL);
   242 
   243     SDL_SendEditingText(utftext, 0, 0);
   244 
   245     env->ReleaseStringUTFChars(text, utftext);
   246 }
   247 
   248 
   249 
   250 /*******************************************************************************
   251              Functions called by SDL into Java
   252 *******************************************************************************/
   253 
   254 class LocalReferenceHolder
   255 {
   256 private:
   257     static int s_active;
   258 
   259 public:
   260     static bool IsActive() {
   261         return s_active > 0;
   262     }
   263 
   264 public:
   265     LocalReferenceHolder(const char *func) : m_env(NULL), m_func(func) {
   266 #ifdef DEBUG_JNI
   267         SDL_Log("Entering function %s", m_func);
   268 #endif
   269     }
   270     ~LocalReferenceHolder() {
   271 #ifdef DEBUG_JNI
   272         SDL_Log("Leaving function %s", m_func);
   273 #endif
   274         if (m_env) {
   275             m_env->PopLocalFrame(NULL);
   276             --s_active;
   277         }
   278     }
   279 
   280     bool init(JNIEnv *env, jint capacity = 16) {
   281         if (env->PushLocalFrame(capacity) < 0) {
   282             SDL_SetError("Failed to allocate enough JVM local references");
   283             return false;
   284         }
   285         ++s_active;
   286         m_env = env;
   287         return true;
   288     }
   289 
   290 protected:
   291     JNIEnv *m_env;
   292     const char *m_func;
   293 };
   294 int LocalReferenceHolder::s_active;
   295 
   296 extern "C" SDL_bool Android_JNI_CreateContext(int majorVersion, int minorVersion,
   297                                 int red, int green, int blue, int alpha,
   298                                 int buffer, int depth, int stencil,
   299                                 int buffers, int samples)
   300 {
   301     JNIEnv *env = Android_JNI_GetEnv();
   302 
   303     jint attribs[] = {
   304         EGL_RED_SIZE, red,
   305         EGL_GREEN_SIZE, green,
   306         EGL_BLUE_SIZE, blue,
   307         EGL_ALPHA_SIZE, alpha,
   308         EGL_BUFFER_SIZE, buffer,
   309         EGL_DEPTH_SIZE, depth,
   310         EGL_STENCIL_SIZE, stencil,
   311         EGL_SAMPLE_BUFFERS, buffers,
   312         EGL_SAMPLES, samples,
   313         EGL_RENDERABLE_TYPE, (majorVersion == 1 ? EGL_OPENGL_ES_BIT : EGL_OPENGL_ES2_BIT),
   314         EGL_NONE
   315     };
   316     int len = SDL_arraysize(attribs);
   317 
   318     jintArray array;
   319 
   320     array = env->NewIntArray(len);
   321     env->SetIntArrayRegion(array, 0, len, attribs);
   322 
   323     jboolean success = env->CallStaticBooleanMethod(mActivityClass, midCreateGLContext, majorVersion, minorVersion, array);
   324 
   325     env->DeleteLocalRef(array);
   326 
   327     return success ? SDL_TRUE : SDL_FALSE;
   328 }
   329 
   330 extern "C" void Android_JNI_SwapWindow()
   331 {
   332     JNIEnv *mEnv = Android_JNI_GetEnv();
   333     mEnv->CallStaticVoidMethod(mActivityClass, midFlipBuffers); 
   334 }
   335 
   336 extern "C" void Android_JNI_SetActivityTitle(const char *title)
   337 {
   338     jmethodID mid;
   339     JNIEnv *mEnv = Android_JNI_GetEnv();
   340     mid = mEnv->GetStaticMethodID(mActivityClass,"setActivityTitle","(Ljava/lang/String;)V");
   341     if (mid) {
   342         jstring jtitle = reinterpret_cast<jstring>(mEnv->NewStringUTF(title));
   343         mEnv->CallStaticVoidMethod(mActivityClass, mid, jtitle);
   344         mEnv->DeleteLocalRef(jtitle);
   345     }
   346 }
   347 
   348 extern "C" SDL_bool Android_JNI_GetAccelerometerValues(float values[3])
   349 {
   350     int i;
   351     SDL_bool retval = SDL_FALSE;
   352 
   353     if (bHasNewData) {
   354         for (i = 0; i < 3; ++i) {
   355             values[i] = fLastAccelerometer[i];
   356         }
   357         bHasNewData = false;
   358         retval = SDL_TRUE;
   359     }
   360 
   361     return retval;
   362 }
   363 
   364 static void Android_JNI_ThreadDestroyed(void* value) {
   365     /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */
   366     JNIEnv *env = (JNIEnv*) value;
   367     if (env != NULL) {
   368         mJavaVM->DetachCurrentThread();
   369         pthread_setspecific(mThreadKey, NULL);
   370     }
   371 }
   372 
   373 JNIEnv* Android_JNI_GetEnv(void) {
   374     /* From http://developer.android.com/guide/practices/jni.html
   375      * All threads are Linux threads, scheduled by the kernel.
   376      * They're usually started from managed code (using Thread.start), but they can also be created elsewhere and then
   377      * attached to the JavaVM. For example, a thread started with pthread_create can be attached with the
   378      * JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a thread is attached, it has no JNIEnv,
   379      * and cannot make JNI calls.
   380      * Attaching a natively-created thread causes a java.lang.Thread object to be constructed and added to the "main"
   381      * ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread on an already-attached thread
   382      * is a no-op.
   383      * Note: You can call this function any number of times for the same thread, there's no harm in it
   384      */
   385 
   386     JNIEnv *env;
   387     int status = mJavaVM->AttachCurrentThread(&env, NULL);
   388     if(status < 0) {
   389         LOGE("failed to attach current thread");
   390         return 0;
   391     }
   392 
   393     return env;
   394 }
   395 
   396 int Android_JNI_SetupThread(void) {
   397     /* From http://developer.android.com/guide/practices/jni.html
   398      * Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward,
   399      * in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be
   400      * called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific
   401      * to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.)
   402      * Note: The destructor is not called unless the stored value is != NULL
   403      * Note: You can call this function any number of times for the same thread, there's no harm in it
   404      *       (except for some lost CPU cycles)
   405      */
   406     JNIEnv *env = Android_JNI_GetEnv();
   407     pthread_setspecific(mThreadKey, (void*) env);
   408     return 1;
   409 }
   410 
   411 //
   412 // Audio support
   413 //
   414 static jboolean audioBuffer16Bit = JNI_FALSE;
   415 static jboolean audioBufferStereo = JNI_FALSE;
   416 static jobject audioBuffer = NULL;
   417 static void* audioBufferPinned = NULL;
   418 
   419 extern "C" int Android_JNI_OpenAudioDevice(int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
   420 {
   421     int audioBufferFrames;
   422 
   423     int status;
   424     JNIEnv *env = Android_JNI_GetEnv();
   425 
   426     if (!env) {
   427         LOGE("callback_handler: failed to attach current thread");
   428     }
   429     Android_JNI_SetupThread();
   430 
   431     
   432     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device");
   433     audioBuffer16Bit = is16Bit;
   434     audioBufferStereo = channelCount > 1;
   435 
   436     env->CallStaticObjectMethod(mActivityClass, midAudioInit, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames);
   437 
   438     /* Allocating the audio buffer from the Java side and passing it as the return value for audioInit no longer works on
   439      * Android >= 4.2 due to a "stale global reference" error. So now we allocate this buffer directly from this side. */
   440     
   441     if (is16Bit) {
   442         jshortArray audioBufferLocal = env->NewShortArray(desiredBufferFrames * (audioBufferStereo ? 2 : 1));
   443         if (audioBufferLocal) {
   444             audioBuffer = env->NewGlobalRef(audioBufferLocal);
   445             env->DeleteLocalRef(audioBufferLocal);
   446         }
   447     }
   448     else {
   449         jbyteArray audioBufferLocal = env->NewByteArray(desiredBufferFrames * (audioBufferStereo ? 2 : 1));
   450         if (audioBufferLocal) {
   451             audioBuffer = env->NewGlobalRef(audioBufferLocal);
   452             env->DeleteLocalRef(audioBufferLocal);
   453         }
   454     }
   455 
   456     if (audioBuffer == NULL) {
   457         __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: could not allocate an audio buffer!");
   458         return 0;
   459     }
   460 
   461     jboolean isCopy = JNI_FALSE;
   462     if (audioBuffer16Bit) {
   463         audioBufferPinned = env->GetShortArrayElements((jshortArray)audioBuffer, &isCopy);
   464         audioBufferFrames = env->GetArrayLength((jshortArray)audioBuffer);
   465     } else {
   466         audioBufferPinned = env->GetByteArrayElements((jbyteArray)audioBuffer, &isCopy);
   467         audioBufferFrames = env->GetArrayLength((jbyteArray)audioBuffer);
   468     }
   469     if (audioBufferStereo) {
   470         audioBufferFrames /= 2;
   471     }
   472  
   473     return audioBufferFrames;
   474 }
   475 
   476 extern "C" void * Android_JNI_GetAudioBuffer()
   477 {
   478     return audioBufferPinned;
   479 }
   480 
   481 extern "C" void Android_JNI_WriteAudioBuffer()
   482 {
   483     JNIEnv *mAudioEnv = Android_JNI_GetEnv();
   484 
   485     if (audioBuffer16Bit) {
   486         mAudioEnv->ReleaseShortArrayElements((jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
   487         mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
   488     } else {
   489         mAudioEnv->ReleaseByteArrayElements((jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT);
   490         mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer);
   491     }
   492 
   493     /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
   494 }
   495 
   496 extern "C" void Android_JNI_CloseAudioDevice()
   497 {
   498     int status;
   499     JNIEnv *env = Android_JNI_GetEnv();
   500 
   501     env->CallStaticVoidMethod(mActivityClass, midAudioQuit); 
   502 
   503     if (audioBuffer) {
   504         env->DeleteGlobalRef(audioBuffer);
   505         audioBuffer = NULL;
   506         audioBufferPinned = NULL;
   507     }
   508 }
   509 
   510 // Test for an exception and call SDL_SetError with its detail if one occurs
   511 static bool Android_JNI_ExceptionOccurred()
   512 {
   513     SDL_assert(LocalReferenceHolder::IsActive());
   514     JNIEnv *mEnv = Android_JNI_GetEnv();
   515 
   516     jthrowable exception = mEnv->ExceptionOccurred();
   517     if (exception != NULL) {
   518         jmethodID mid;
   519 
   520         // Until this happens most JNI operations have undefined behaviour
   521         mEnv->ExceptionClear();
   522 
   523         jclass exceptionClass = mEnv->GetObjectClass(exception);
   524         jclass classClass = mEnv->FindClass("java/lang/Class");
   525 
   526         mid = mEnv->GetMethodID(classClass, "getName", "()Ljava/lang/String;");
   527         jstring exceptionName = (jstring)mEnv->CallObjectMethod(exceptionClass, mid);
   528         const char* exceptionNameUTF8 = mEnv->GetStringUTFChars(exceptionName, 0);
   529 
   530         mid = mEnv->GetMethodID(exceptionClass, "getMessage", "()Ljava/lang/String;");
   531         jstring exceptionMessage = (jstring)mEnv->CallObjectMethod(exception, mid);
   532 
   533         if (exceptionMessage != NULL) {
   534             const char* exceptionMessageUTF8 = mEnv->GetStringUTFChars(
   535                     exceptionMessage, 0);
   536             SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
   537             mEnv->ReleaseStringUTFChars(exceptionMessage, exceptionMessageUTF8);
   538         } else {
   539             SDL_SetError("%s", exceptionNameUTF8);
   540         }
   541 
   542         mEnv->ReleaseStringUTFChars(exceptionName, exceptionNameUTF8);
   543 
   544         return true;
   545     }
   546 
   547     return false;
   548 }
   549 
   550 static int Android_JNI_FileOpen(SDL_RWops* ctx)
   551 {
   552     LocalReferenceHolder refs(__FUNCTION__);
   553     int result = 0;
   554 
   555     jmethodID mid;
   556     jobject context;
   557     jobject assetManager;
   558     jobject inputStream;
   559     jclass channels;
   560     jobject readableByteChannel;
   561     jstring fileNameJString;
   562 
   563     JNIEnv *mEnv = Android_JNI_GetEnv();
   564     if (!refs.init(mEnv)) {
   565         goto failure;
   566     }
   567 
   568     fileNameJString = (jstring)ctx->hidden.androidio.fileNameRef;
   569 
   570     // context = SDLActivity.getContext();
   571     mid = mEnv->GetStaticMethodID(mActivityClass,
   572             "getContext","()Landroid/content/Context;");
   573     context = mEnv->CallStaticObjectMethod(mActivityClass, mid);
   574 
   575     // assetManager = context.getAssets();
   576     mid = mEnv->GetMethodID(mEnv->GetObjectClass(context),
   577             "getAssets", "()Landroid/content/res/AssetManager;");
   578     assetManager = mEnv->CallObjectMethod(context, mid);
   579 
   580     // inputStream = assetManager.open(<filename>);
   581     mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager),
   582             "open", "(Ljava/lang/String;)Ljava/io/InputStream;");
   583     inputStream = mEnv->CallObjectMethod(assetManager, mid, fileNameJString);
   584     if (Android_JNI_ExceptionOccurred()) {
   585         goto failure;
   586     }
   587 
   588     ctx->hidden.androidio.inputStreamRef = mEnv->NewGlobalRef(inputStream);
   589 
   590     // Despite all the visible documentation on [Asset]InputStream claiming
   591     // that the .available() method is not guaranteed to return the entire file
   592     // size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
   593     // android/apis/content/ReadAsset.java imply that Android's
   594     // AssetInputStream.available() /will/ always return the total file size
   595 
   596     // size = inputStream.available();
   597     mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   598             "available", "()I");
   599     ctx->hidden.androidio.size = mEnv->CallIntMethod(inputStream, mid);
   600     if (Android_JNI_ExceptionOccurred()) {
   601         goto failure;
   602     }
   603 
   604     // readableByteChannel = Channels.newChannel(inputStream);
   605     channels = mEnv->FindClass("java/nio/channels/Channels");
   606     mid = mEnv->GetStaticMethodID(channels,
   607             "newChannel",
   608             "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
   609     readableByteChannel = mEnv->CallStaticObjectMethod(
   610             channels, mid, inputStream);
   611     if (Android_JNI_ExceptionOccurred()) {
   612         goto failure;
   613     }
   614 
   615     ctx->hidden.androidio.readableByteChannelRef =
   616         mEnv->NewGlobalRef(readableByteChannel);
   617 
   618     // Store .read id for reading purposes
   619     mid = mEnv->GetMethodID(mEnv->GetObjectClass(readableByteChannel),
   620             "read", "(Ljava/nio/ByteBuffer;)I");
   621     ctx->hidden.androidio.readMethod = mid;
   622 
   623     ctx->hidden.androidio.position = 0;
   624 
   625     if (false) {
   626 failure:
   627         result = -1;
   628 
   629         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   630 
   631         if(ctx->hidden.androidio.inputStreamRef != NULL) {
   632             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   633         }
   634 
   635         if(ctx->hidden.androidio.readableByteChannelRef != NULL) {
   636             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
   637         }
   638 
   639     }
   640 
   641     return result;
   642 }
   643 
   644 extern "C" int Android_JNI_FileOpen(SDL_RWops* ctx,
   645         const char* fileName, const char*)
   646 {
   647     LocalReferenceHolder refs(__FUNCTION__);
   648     JNIEnv *mEnv = Android_JNI_GetEnv();
   649 
   650     if (!refs.init(mEnv)) {
   651         return -1;
   652     }
   653 
   654     if (!ctx) {
   655         return -1;
   656     }
   657 
   658     jstring fileNameJString = mEnv->NewStringUTF(fileName);
   659     ctx->hidden.androidio.fileNameRef = mEnv->NewGlobalRef(fileNameJString);
   660     ctx->hidden.androidio.inputStreamRef = NULL;
   661     ctx->hidden.androidio.readableByteChannelRef = NULL;
   662     ctx->hidden.androidio.readMethod = NULL;
   663 
   664     return Android_JNI_FileOpen(ctx);
   665 }
   666 
   667 extern "C" size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
   668         size_t size, size_t maxnum)
   669 {
   670     LocalReferenceHolder refs(__FUNCTION__);
   671     jlong bytesRemaining = (jlong) (size * maxnum);
   672     jlong bytesMax = (jlong) (ctx->hidden.androidio.size -  ctx->hidden.androidio.position);
   673     int bytesRead = 0;
   674 
   675     /* Don't read more bytes than those that remain in the file, otherwise we get an exception */
   676     if (bytesRemaining >  bytesMax) bytesRemaining = bytesMax;
   677 
   678     JNIEnv *mEnv = Android_JNI_GetEnv();
   679     if (!refs.init(mEnv)) {
   680         return -1;
   681     }
   682 
   683     jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannelRef;
   684     jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
   685     jobject byteBuffer = mEnv->NewDirectByteBuffer(buffer, bytesRemaining);
   686 
   687     while (bytesRemaining > 0) {
   688         // result = readableByteChannel.read(...);
   689         int result = mEnv->CallIntMethod(readableByteChannel, readMethod, byteBuffer);
   690 
   691         if (Android_JNI_ExceptionOccurred()) {
   692             return 0;
   693         }
   694 
   695         if (result < 0) {
   696             break;
   697         }
   698 
   699         bytesRemaining -= result;
   700         bytesRead += result;
   701         ctx->hidden.androidio.position += result;
   702     }
   703 
   704     return bytesRead / size;
   705 }
   706 
   707 extern "C" size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
   708         size_t size, size_t num)
   709 {
   710     SDL_SetError("Cannot write to Android package filesystem");
   711     return 0;
   712 }
   713 
   714 static int Android_JNI_FileClose(SDL_RWops* ctx, bool release)
   715 {
   716     LocalReferenceHolder refs(__FUNCTION__);
   717     int result = 0;
   718     JNIEnv *mEnv = Android_JNI_GetEnv();
   719 
   720     if (!refs.init(mEnv)) {
   721         SDL_SetError("Failed to allocate enough JVM local references");
   722         return -1;
   723     }
   724 
   725     if (ctx) {
   726         if (release) {
   727             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   728         }
   729 
   730         jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
   731 
   732         // inputStream.close();
   733         jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   734                 "close", "()V");
   735         mEnv->CallVoidMethod(inputStream, mid);
   736         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   737         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
   738         if (Android_JNI_ExceptionOccurred()) {
   739             result = -1;
   740         }
   741 
   742         if (release) {
   743             SDL_FreeRW(ctx);
   744         }
   745     }
   746 
   747     return result;
   748 }
   749 
   750 
   751 extern "C" Sint64 Android_JNI_FileSize(SDL_RWops* ctx)
   752 {
   753     return ctx->hidden.androidio.size;
   754 }
   755 
   756 extern "C" Sint64 Android_JNI_FileSeek(SDL_RWops* ctx, Sint64 offset, int whence)
   757 {
   758     Sint64 newPosition;
   759 
   760     switch (whence) {
   761         case RW_SEEK_SET:
   762             newPosition = offset;
   763             break;
   764         case RW_SEEK_CUR:
   765             newPosition = ctx->hidden.androidio.position + offset;
   766             break;
   767         case RW_SEEK_END:
   768             newPosition = ctx->hidden.androidio.size + offset;
   769             break;
   770         default:
   771             SDL_SetError("Unknown value for 'whence'");
   772             return -1;
   773     }
   774 
   775     /* Validate the new position */
   776     if (newPosition < 0) {
   777         SDL_Error(SDL_EFSEEK);
   778         return -1;
   779     }
   780     if (newPosition > ctx->hidden.androidio.size) {
   781         newPosition = ctx->hidden.androidio.size;
   782     }
   783 
   784     Sint64 movement = newPosition - ctx->hidden.androidio.position;
   785     if (movement > 0) {
   786         unsigned char buffer[4096];
   787 
   788         // The easy case where we're seeking forwards
   789         while (movement > 0) {
   790             Sint64 amount = sizeof (buffer);
   791             if (amount > movement) {
   792                 amount = movement;
   793             }
   794             size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount);
   795             if (result <= 0) {
   796                 // Failed to read/skip the required amount, so fail
   797                 return -1;
   798             }
   799 
   800             movement -= result;
   801         }
   802 
   803     } else if (movement < 0) {
   804         // We can't seek backwards so we have to reopen the file and seek
   805         // forwards which obviously isn't very efficient
   806         Android_JNI_FileClose(ctx, false);
   807         Android_JNI_FileOpen(ctx);
   808         Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
   809     }
   810 
   811     return ctx->hidden.androidio.position;
   812 }
   813 
   814 extern "C" int Android_JNI_FileClose(SDL_RWops* ctx)
   815 {
   816     return Android_JNI_FileClose(ctx, true);
   817 }
   818 
   819 // returns a new global reference which needs to be released later
   820 static jobject Android_JNI_GetSystemServiceObject(const char* name)
   821 {
   822     LocalReferenceHolder refs(__FUNCTION__);
   823     JNIEnv* env = Android_JNI_GetEnv();
   824     if (!refs.init(env)) {
   825         return NULL;
   826     }
   827 
   828     jstring service = env->NewStringUTF(name);
   829 
   830     jmethodID mid;
   831 
   832     mid = env->GetStaticMethodID(mActivityClass, "getContext", "()Landroid/content/Context;");
   833     jobject context = env->CallStaticObjectMethod(mActivityClass, mid);
   834 
   835     mid = env->GetMethodID(mActivityClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
   836     jobject manager = env->CallObjectMethod(context, mid, service);
   837 
   838     env->DeleteLocalRef(service);
   839 
   840     return manager ? env->NewGlobalRef(manager) : NULL;
   841 }
   842 
   843 #define SETUP_CLIPBOARD(error) \
   844     LocalReferenceHolder refs(__FUNCTION__); \
   845     JNIEnv* env = Android_JNI_GetEnv(); \
   846     if (!refs.init(env)) { \
   847         return error; \
   848     } \
   849     jobject clipboard = Android_JNI_GetSystemServiceObject("clipboard"); \
   850     if (!clipboard) { \
   851         return error; \
   852     }
   853 
   854 extern "C" int Android_JNI_SetClipboardText(const char* text)
   855 {
   856     SETUP_CLIPBOARD(-1)
   857 
   858     jmethodID mid = env->GetMethodID(env->GetObjectClass(clipboard), "setText", "(Ljava/lang/CharSequence;)V");
   859     jstring string = env->NewStringUTF(text);
   860     env->CallVoidMethod(clipboard, mid, string);
   861     env->DeleteGlobalRef(clipboard);
   862     env->DeleteLocalRef(string);
   863     return 0;
   864 }
   865 
   866 extern "C" char* Android_JNI_GetClipboardText()
   867 {
   868     SETUP_CLIPBOARD(SDL_strdup(""))
   869 
   870     jmethodID mid = env->GetMethodID(env->GetObjectClass(clipboard), "getText", "()Ljava/lang/CharSequence;");
   871     jobject sequence = env->CallObjectMethod(clipboard, mid);
   872     env->DeleteGlobalRef(clipboard);
   873     if (sequence) {
   874         mid = env->GetMethodID(env->GetObjectClass(sequence), "toString", "()Ljava/lang/String;");
   875         jstring string = reinterpret_cast<jstring>(env->CallObjectMethod(sequence, mid));
   876         const char* utf = env->GetStringUTFChars(string, 0);
   877         if (utf) {
   878             char* text = SDL_strdup(utf);
   879             env->ReleaseStringUTFChars(string, utf);
   880             return text;
   881         }
   882     }
   883     return SDL_strdup("");
   884 }
   885 
   886 extern "C" SDL_bool Android_JNI_HasClipboardText()
   887 {
   888     SETUP_CLIPBOARD(SDL_FALSE)
   889 
   890     jmethodID mid = env->GetMethodID(env->GetObjectClass(clipboard), "hasText", "()Z");
   891     jboolean has = env->CallBooleanMethod(clipboard, mid);
   892     env->DeleteGlobalRef(clipboard);
   893     return has ? SDL_TRUE : SDL_FALSE;
   894 }
   895 
   896 
   897 // returns 0 on success or -1 on error (others undefined then)
   898 // returns truthy or falsy value in plugged, charged and battery
   899 // returns the value in seconds and percent or -1 if not available
   900 extern "C" int Android_JNI_GetPowerInfo(int* plugged, int* charged, int* battery, int* seconds, int* percent)
   901 {
   902     LocalReferenceHolder refs(__FUNCTION__);
   903     JNIEnv* env = Android_JNI_GetEnv();
   904     if (!refs.init(env)) {
   905         return -1;
   906     }
   907 
   908     jmethodID mid;
   909 
   910     mid = env->GetStaticMethodID(mActivityClass, "getContext", "()Landroid/content/Context;");
   911     jobject context = env->CallStaticObjectMethod(mActivityClass, mid);
   912 
   913     jstring action = env->NewStringUTF("android.intent.action.BATTERY_CHANGED");
   914 
   915     jclass cls = env->FindClass("android/content/IntentFilter");
   916 
   917     mid = env->GetMethodID(cls, "<init>", "(Ljava/lang/String;)V");
   918     jobject filter = env->NewObject(cls, mid, action);
   919 
   920     env->DeleteLocalRef(action);
   921 
   922     mid = env->GetMethodID(mActivityClass, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;");
   923     jobject intent = env->CallObjectMethod(context, mid, NULL, filter);
   924 
   925     env->DeleteLocalRef(filter);
   926 
   927     cls = env->GetObjectClass(intent);
   928 
   929     jstring iname;
   930     jmethodID imid = env->GetMethodID(cls, "getIntExtra", "(Ljava/lang/String;I)I");
   931 
   932 #define GET_INT_EXTRA(var, key) \
   933     iname = env->NewStringUTF(key); \
   934     int var = env->CallIntMethod(intent, imid, iname, -1); \
   935     env->DeleteLocalRef(iname);
   936 
   937     jstring bname;
   938     jmethodID bmid = env->GetMethodID(cls, "getBooleanExtra", "(Ljava/lang/String;Z)Z");
   939 
   940 #define GET_BOOL_EXTRA(var, key) \
   941     bname = env->NewStringUTF(key); \
   942     int var = env->CallBooleanMethod(intent, bmid, bname, JNI_FALSE); \
   943     env->DeleteLocalRef(bname);
   944 
   945     if (plugged) {
   946         GET_INT_EXTRA(plug, "plugged") // == BatteryManager.EXTRA_PLUGGED (API 5)
   947         if (plug == -1) {
   948             return -1;
   949         }
   950         // 1 == BatteryManager.BATTERY_PLUGGED_AC
   951         // 2 == BatteryManager.BATTERY_PLUGGED_USB
   952         *plugged = (0 < plug) ? 1 : 0;
   953     }
   954 
   955     if (charged) {
   956         GET_INT_EXTRA(status, "status") // == BatteryManager.EXTRA_STATUS (API 5)
   957         if (status == -1) {
   958             return -1;
   959         }
   960         // 5 == BatteryManager.BATTERY_STATUS_FULL
   961         *charged = (status == 5) ? 1 : 0;
   962     }
   963 
   964     if (battery) {
   965         GET_BOOL_EXTRA(present, "present") // == BatteryManager.EXTRA_PRESENT (API 5)
   966         *battery = present ? 1 : 0;
   967     }
   968 
   969     if (seconds) {
   970         *seconds = -1; // not possible
   971     }
   972 
   973     if (percent) {
   974         GET_INT_EXTRA(level, "level") // == BatteryManager.EXTRA_LEVEL (API 5)
   975         GET_INT_EXTRA(scale, "scale") // == BatteryManager.EXTRA_SCALE (API 5)
   976         if ((level == -1) || (scale == -1)) {
   977             return -1;
   978         }
   979         *percent = level * 100 / scale;
   980     }
   981 
   982     env->DeleteLocalRef(intent);
   983 
   984     return 0;
   985 }
   986 
   987 // sends message to be handled on the UI event dispatch thread
   988 extern "C" int Android_JNI_SendMessage(int command, int param)
   989 {
   990     JNIEnv *env = Android_JNI_GetEnv();
   991     if (!env) {
   992         return -1;
   993     }
   994     jmethodID mid = env->GetStaticMethodID(mActivityClass, "sendMessage", "(II)V");
   995     if (!mid) {
   996         return -1;
   997     }
   998     env->CallStaticVoidMethod(mActivityClass, mid, command, param);
   999     return 0;
  1000 }
  1001 
  1002 extern "C" void Android_JNI_ShowTextInput(SDL_Rect *inputRect)
  1003 {
  1004     JNIEnv *env = Android_JNI_GetEnv();
  1005     if (!env) {
  1006         return;
  1007     }
  1008 
  1009     jmethodID mid = env->GetStaticMethodID(mActivityClass, "showTextInput", "(IIII)V");
  1010     if (!mid) {
  1011         return;
  1012     }
  1013     env->CallStaticVoidMethod( mActivityClass, mid,
  1014                                inputRect->x,
  1015                                inputRect->y,
  1016                                inputRect->w,
  1017                                inputRect->h );
  1018 }
  1019 
  1020 extern "C" void Android_JNI_HideTextInput()
  1021 {
  1022     // has to match Activity constant
  1023     const int COMMAND_TEXTEDIT_HIDE = 3;
  1024     Android_JNI_SendMessage(COMMAND_TEXTEDIT_HIDE, 0);
  1025 }
  1026 
  1027 //////////////////////////////////////////////////////////////////////////////
  1028 //
  1029 // Functions exposed to SDL applications in SDL_system.h
  1030 //
  1031 
  1032 extern "C" void *SDL_AndroidGetJNIEnv()
  1033 {
  1034     return Android_JNI_GetEnv();
  1035 }
  1036 
  1037 extern "C" void *SDL_AndroidGetActivity()
  1038 {
  1039     LocalReferenceHolder refs(__FUNCTION__);
  1040     jmethodID mid;
  1041 
  1042     JNIEnv *env = Android_JNI_GetEnv();
  1043     if (!refs.init(env)) {
  1044         return NULL;
  1045     }
  1046 
  1047     // return SDLActivity.getContext();
  1048     mid = env->GetStaticMethodID(mActivityClass,
  1049             "getContext","()Landroid/content/Context;");
  1050     return env->CallStaticObjectMethod(mActivityClass, mid);
  1051 }
  1052 
  1053 extern "C" const char * SDL_AndroidGetInternalStoragePath()
  1054 {
  1055     static char *s_AndroidInternalFilesPath = NULL;
  1056 
  1057     if (!s_AndroidInternalFilesPath) {
  1058         LocalReferenceHolder refs(__FUNCTION__);
  1059         jmethodID mid;
  1060         jobject context;
  1061         jobject fileObject;
  1062         jstring pathString;
  1063         const char *path;
  1064 
  1065         JNIEnv *env = Android_JNI_GetEnv();
  1066         if (!refs.init(env)) {
  1067             return NULL;
  1068         }
  1069 
  1070         // context = SDLActivity.getContext();
  1071         mid = env->GetStaticMethodID(mActivityClass,
  1072                 "getContext","()Landroid/content/Context;");
  1073         context = env->CallStaticObjectMethod(mActivityClass, mid);
  1074 
  1075         // fileObj = context.getFilesDir();
  1076         mid = env->GetMethodID(env->GetObjectClass(context),
  1077                 "getFilesDir", "()Ljava/io/File;");
  1078         fileObject = env->CallObjectMethod(context, mid);
  1079         if (!fileObject) {
  1080             SDL_SetError("Couldn't get internal directory");
  1081             return NULL;
  1082         }
  1083 
  1084         // path = fileObject.getAbsolutePath();
  1085         mid = env->GetMethodID(env->GetObjectClass(fileObject),
  1086                 "getAbsolutePath", "()Ljava/lang/String;");
  1087         pathString = (jstring)env->CallObjectMethod(fileObject, mid);
  1088 
  1089         path = env->GetStringUTFChars(pathString, NULL);
  1090         s_AndroidInternalFilesPath = SDL_strdup(path);
  1091         env->ReleaseStringUTFChars(pathString, path);
  1092     }
  1093     return s_AndroidInternalFilesPath;
  1094 }
  1095 
  1096 extern "C" int SDL_AndroidGetExternalStorageState()
  1097 {
  1098     LocalReferenceHolder refs(__FUNCTION__);
  1099     jmethodID mid;
  1100     jclass cls;
  1101     jstring stateString;
  1102     const char *state;
  1103     int stateFlags;
  1104 
  1105     JNIEnv *env = Android_JNI_GetEnv();
  1106     if (!refs.init(env)) {
  1107         return 0;
  1108     }
  1109 
  1110     cls = env->FindClass("android/os/Environment");
  1111     mid = env->GetStaticMethodID(cls,
  1112             "getExternalStorageState", "()Ljava/lang/String;");
  1113     stateString = (jstring)env->CallStaticObjectMethod(cls, mid);
  1114 
  1115     state = env->GetStringUTFChars(stateString, NULL);
  1116 
  1117     // Print an info message so people debugging know the storage state
  1118     __android_log_print(ANDROID_LOG_INFO, "SDL", "external storage state: %s", state);
  1119 
  1120     if (SDL_strcmp(state, "mounted") == 0) {
  1121         stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ |
  1122                      SDL_ANDROID_EXTERNAL_STORAGE_WRITE;
  1123     } else if (SDL_strcmp(state, "mounted_ro") == 0) {
  1124         stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ;
  1125     } else {
  1126         stateFlags = 0;
  1127     }
  1128     env->ReleaseStringUTFChars(stateString, state);
  1129 
  1130     return stateFlags;
  1131 }
  1132 
  1133 extern "C" const char * SDL_AndroidGetExternalStoragePath()
  1134 {
  1135     static char *s_AndroidExternalFilesPath = NULL;
  1136 
  1137     if (!s_AndroidExternalFilesPath) {
  1138         LocalReferenceHolder refs(__FUNCTION__);
  1139         jmethodID mid;
  1140         jobject context;
  1141         jobject fileObject;
  1142         jstring pathString;
  1143         const char *path;
  1144 
  1145         JNIEnv *env = Android_JNI_GetEnv();
  1146         if (!refs.init(env)) {
  1147             return NULL;
  1148         }
  1149 
  1150         // context = SDLActivity.getContext();
  1151         mid = env->GetStaticMethodID(mActivityClass,
  1152                 "getContext","()Landroid/content/Context;");
  1153         context = env->CallStaticObjectMethod(mActivityClass, mid);
  1154 
  1155         // fileObj = context.getExternalFilesDir();
  1156         mid = env->GetMethodID(env->GetObjectClass(context),
  1157                 "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;");
  1158         fileObject = env->CallObjectMethod(context, mid, NULL);
  1159         if (!fileObject) {
  1160             SDL_SetError("Couldn't get external directory");
  1161             return NULL;
  1162         }
  1163 
  1164         // path = fileObject.getAbsolutePath();
  1165         mid = env->GetMethodID(env->GetObjectClass(fileObject),
  1166                 "getAbsolutePath", "()Ljava/lang/String;");
  1167         pathString = (jstring)env->CallObjectMethod(fileObject, mid);
  1168 
  1169         path = env->GetStringUTFChars(pathString, NULL);
  1170         s_AndroidExternalFilesPath = SDL_strdup(path);
  1171         env->ReleaseStringUTFChars(pathString, path);
  1172     }
  1173     return s_AndroidExternalFilesPath;
  1174 }
  1175 
  1176 #endif /* __ANDROID__ */
  1177 
  1178 /* vi: set ts=4 sw=4 expandtab: */