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