src/core/android/SDL_android.cpp
author Sam Lantinga <slouken@libsdl.org>
Sun, 04 Nov 2012 20:20:53 -0800
changeset 6653 b73f8ee5f033
parent 6650 d36232135316
child 6654 2ecfb25be1e2
permissions -rw-r--r--
Whitespace cleanup
     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              Functions called by SDL into Java
   251 *******************************************************************************/
   252 
   253 class LocalReferenceHolder
   254 {
   255 private:
   256     static int s_active;
   257 
   258 public:
   259     static bool IsActive() {
   260         return s_active > 0;
   261     }
   262 
   263 public:
   264     LocalReferenceHolder(const char *func) : m_env(NULL), m_func(func) {
   265 #ifdef DEBUG_JNI
   266         SDL_Log("Entering function %s", m_func);
   267 #endif
   268     }
   269     ~LocalReferenceHolder() {
   270 #ifdef DEBUG_JNI
   271         SDL_Log("Leaving function %s", m_func);
   272 #endif
   273         if (m_env) {
   274             m_env->PopLocalFrame(NULL);
   275             --s_active;
   276         }
   277     }
   278 
   279     bool init(JNIEnv *env, jint capacity = 16) {
   280         if (env->PushLocalFrame(capacity) < 0) {
   281             SDL_SetError("Failed to allocate enough JVM local references");
   282             return false;
   283         }
   284         ++s_active;
   285         m_env = env;
   286         return true;
   287     }
   288 
   289 protected:
   290     JNIEnv *m_env;
   291     const char *m_func;
   292 };
   293 int LocalReferenceHolder::s_active;
   294 
   295 extern "C" SDL_bool Android_JNI_CreateContext(int majorVersion, int minorVersion)
   296 {
   297     JNIEnv *mEnv = Android_JNI_GetEnv();
   298     if (mEnv->CallStaticBooleanMethod(mActivityClass, midCreateGLContext, majorVersion, minorVersion)) {
   299         return SDL_TRUE;
   300     } else {
   301         return SDL_FALSE;
   302     }
   303 }
   304 
   305 extern "C" void Android_JNI_SwapWindow()
   306 {
   307     JNIEnv *mEnv = Android_JNI_GetEnv();
   308     mEnv->CallStaticVoidMethod(mActivityClass, midFlipBuffers); 
   309 }
   310 
   311 extern "C" void Android_JNI_SetActivityTitle(const char *title)
   312 {
   313     jmethodID mid;
   314     JNIEnv *mEnv = Android_JNI_GetEnv();
   315     mid = mEnv->GetStaticMethodID(mActivityClass,"setActivityTitle","(Ljava/lang/String;)V");
   316     if (mid) {
   317         jstring jtitle = reinterpret_cast<jstring>(mEnv->NewStringUTF(title));
   318         mEnv->CallStaticVoidMethod(mActivityClass, mid, jtitle);
   319         mEnv->DeleteLocalRef(jtitle);
   320     }
   321 }
   322 
   323 extern "C" SDL_bool Android_JNI_GetAccelerometerValues(float values[3])
   324 {
   325     int i;
   326     SDL_bool retval = SDL_FALSE;
   327 
   328     if (bHasNewData) {
   329         for (i = 0; i < 3; ++i) {
   330             values[i] = fLastAccelerometer[i];
   331         }
   332         bHasNewData = false;
   333         retval = SDL_TRUE;
   334     }
   335 
   336     return retval;
   337 }
   338 
   339 static void Android_JNI_ThreadDestroyed(void* value) {
   340     /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */
   341     JNIEnv *env = (JNIEnv*) value;
   342     if (env != NULL) {
   343         mJavaVM->DetachCurrentThread();
   344         pthread_setspecific(mThreadKey, NULL);
   345     }
   346 }
   347 
   348 JNIEnv* Android_JNI_GetEnv(void) {
   349     /* From http://developer.android.com/guide/practices/jni.html
   350      * All threads are Linux threads, scheduled by the kernel.
   351      * They're usually started from managed code (using Thread.start), but they can also be created elsewhere and then
   352      * attached to the JavaVM. For example, a thread started with pthread_create can be attached with the
   353      * JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a thread is attached, it has no JNIEnv,
   354      * and cannot make JNI calls.
   355      * Attaching a natively-created thread causes a java.lang.Thread object to be constructed and added to the "main"
   356      * ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread on an already-attached thread
   357      * is a no-op.
   358      * Note: You can call this function any number of times for the same thread, there's no harm in it
   359      */
   360 
   361     JNIEnv *env;
   362     int status = mJavaVM->AttachCurrentThread(&env, NULL);
   363     if(status < 0) {
   364         LOGE("failed to attach current thread");
   365         return 0;
   366     }
   367 
   368     return env;
   369 }
   370 
   371 int Android_JNI_SetupThread(void) {
   372     /* From http://developer.android.com/guide/practices/jni.html
   373      * Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward,
   374      * in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be
   375      * called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific
   376      * to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.)
   377      * Note: The destructor is not called unless the stored value is != NULL
   378      * Note: You can call this function any number of times for the same thread, there's no harm in it
   379      *       (except for some lost CPU cycles)
   380      */
   381     JNIEnv *env = Android_JNI_GetEnv();
   382     pthread_setspecific(mThreadKey, (void*) env);
   383     return 1;
   384 }
   385 
   386 //
   387 // Audio support
   388 //
   389 static jboolean audioBuffer16Bit = JNI_FALSE;
   390 static jboolean audioBufferStereo = JNI_FALSE;
   391 static jobject audioBuffer = NULL;
   392 static void* audioBufferPinned = NULL;
   393 
   394 extern "C" int Android_JNI_OpenAudioDevice(int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
   395 {
   396     int audioBufferFrames;
   397 
   398     int status;
   399     JNIEnv *env = Android_JNI_GetEnv();
   400 
   401     if (!env) {
   402         LOGE("callback_handler: failed to attach current thread");
   403     }
   404     Android_JNI_SetupThread();
   405 
   406     
   407     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device");
   408     audioBuffer16Bit = is16Bit;
   409     audioBufferStereo = channelCount > 1;
   410 
   411     audioBuffer = env->CallStaticObjectMethod(mActivityClass, midAudioInit, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames);
   412 
   413     if (audioBuffer == NULL) {
   414         __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: didn't get back a good audio buffer!");
   415         return 0;
   416     }
   417     audioBuffer = env->NewGlobalRef(audioBuffer);
   418 
   419     jboolean isCopy = JNI_FALSE;
   420     if (audioBuffer16Bit) {
   421         audioBufferPinned = env->GetShortArrayElements((jshortArray)audioBuffer, &isCopy);
   422         audioBufferFrames = env->GetArrayLength((jshortArray)audioBuffer);
   423     } else {
   424         audioBufferPinned = env->GetByteArrayElements((jbyteArray)audioBuffer, &isCopy);
   425         audioBufferFrames = env->GetArrayLength((jbyteArray)audioBuffer);
   426     }
   427     if (audioBufferStereo) {
   428         audioBufferFrames /= 2;
   429     }
   430  
   431     return audioBufferFrames;
   432 }
   433 
   434 extern "C" void * Android_JNI_GetAudioBuffer()
   435 {
   436     return audioBufferPinned;
   437 }
   438 
   439 extern "C" void Android_JNI_WriteAudioBuffer()
   440 {
   441     JNIEnv *mAudioEnv = Android_JNI_GetEnv();
   442 
   443     if (audioBuffer16Bit) {
   444         mAudioEnv->ReleaseShortArrayElements((jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
   445         mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
   446     } else {
   447         mAudioEnv->ReleaseByteArrayElements((jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT);
   448         mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer);
   449     }
   450 
   451     /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
   452 }
   453 
   454 extern "C" void Android_JNI_CloseAudioDevice()
   455 {
   456     int status;
   457     JNIEnv *env = Android_JNI_GetEnv();
   458 
   459     env->CallStaticVoidMethod(mActivityClass, midAudioQuit); 
   460 
   461     if (audioBuffer) {
   462         env->DeleteGlobalRef(audioBuffer);
   463         audioBuffer = NULL;
   464         audioBufferPinned = NULL;
   465     }
   466 }
   467 
   468 // Test for an exception and call SDL_SetError with its detail if one occurs
   469 static bool Android_JNI_ExceptionOccurred()
   470 {
   471     SDL_assert(LocalReferenceHolder::IsActive());
   472     JNIEnv *mEnv = Android_JNI_GetEnv();
   473 
   474     jthrowable exception = mEnv->ExceptionOccurred();
   475     if (exception != NULL) {
   476         jmethodID mid;
   477 
   478         // Until this happens most JNI operations have undefined behaviour
   479         mEnv->ExceptionClear();
   480 
   481         jclass exceptionClass = mEnv->GetObjectClass(exception);
   482         jclass classClass = mEnv->FindClass("java/lang/Class");
   483 
   484         mid = mEnv->GetMethodID(classClass, "getName", "()Ljava/lang/String;");
   485         jstring exceptionName = (jstring)mEnv->CallObjectMethod(exceptionClass, mid);
   486         const char* exceptionNameUTF8 = mEnv->GetStringUTFChars(exceptionName, 0);
   487 
   488         mid = mEnv->GetMethodID(exceptionClass, "getMessage", "()Ljava/lang/String;");
   489         jstring exceptionMessage = (jstring)mEnv->CallObjectMethod(exception, mid);
   490 
   491         if (exceptionMessage != NULL) {
   492             const char* exceptionMessageUTF8 = mEnv->GetStringUTFChars(
   493                     exceptionMessage, 0);
   494             SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
   495             mEnv->ReleaseStringUTFChars(exceptionMessage, exceptionMessageUTF8);
   496         } else {
   497             SDL_SetError("%s", exceptionNameUTF8);
   498         }
   499 
   500         mEnv->ReleaseStringUTFChars(exceptionName, exceptionNameUTF8);
   501 
   502         return true;
   503     }
   504 
   505     return false;
   506 }
   507 
   508 static int Android_JNI_FileOpen(SDL_RWops* ctx)
   509 {
   510     LocalReferenceHolder refs(__FUNCTION__);
   511     int result = 0;
   512 
   513     jmethodID mid;
   514     jobject context;
   515     jobject assetManager;
   516     jobject inputStream;
   517     jclass channels;
   518     jobject readableByteChannel;
   519     jstring fileNameJString;
   520 
   521     JNIEnv *mEnv = Android_JNI_GetEnv();
   522     if (!refs.init(mEnv)) {
   523         goto failure;
   524     }
   525 
   526     fileNameJString = (jstring)ctx->hidden.androidio.fileNameRef;
   527 
   528     // context = SDLActivity.getContext();
   529     mid = mEnv->GetStaticMethodID(mActivityClass,
   530             "getContext","()Landroid/content/Context;");
   531     context = mEnv->CallStaticObjectMethod(mActivityClass, mid);
   532 
   533     // assetManager = context.getAssets();
   534     mid = mEnv->GetMethodID(mEnv->GetObjectClass(context),
   535             "getAssets", "()Landroid/content/res/AssetManager;");
   536     assetManager = mEnv->CallObjectMethod(context, mid);
   537 
   538     // inputStream = assetManager.open(<filename>);
   539     mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager),
   540             "open", "(Ljava/lang/String;)Ljava/io/InputStream;");
   541     inputStream = mEnv->CallObjectMethod(assetManager, mid, fileNameJString);
   542     if (Android_JNI_ExceptionOccurred()) {
   543         goto failure;
   544     }
   545 
   546     ctx->hidden.androidio.inputStreamRef = mEnv->NewGlobalRef(inputStream);
   547 
   548     // Despite all the visible documentation on [Asset]InputStream claiming
   549     // that the .available() method is not guaranteed to return the entire file
   550     // size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
   551     // android/apis/content/ReadAsset.java imply that Android's
   552     // AssetInputStream.available() /will/ always return the total file size
   553 
   554     // size = inputStream.available();
   555     mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   556             "available", "()I");
   557     ctx->hidden.androidio.size = mEnv->CallIntMethod(inputStream, mid);
   558     if (Android_JNI_ExceptionOccurred()) {
   559         goto failure;
   560     }
   561 
   562     // readableByteChannel = Channels.newChannel(inputStream);
   563     channels = mEnv->FindClass("java/nio/channels/Channels");
   564     mid = mEnv->GetStaticMethodID(channels,
   565             "newChannel",
   566             "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
   567     readableByteChannel = mEnv->CallStaticObjectMethod(
   568             channels, mid, inputStream);
   569     if (Android_JNI_ExceptionOccurred()) {
   570         goto failure;
   571     }
   572 
   573     ctx->hidden.androidio.readableByteChannelRef =
   574         mEnv->NewGlobalRef(readableByteChannel);
   575 
   576     // Store .read id for reading purposes
   577     mid = mEnv->GetMethodID(mEnv->GetObjectClass(readableByteChannel),
   578             "read", "(Ljava/nio/ByteBuffer;)I");
   579     ctx->hidden.androidio.readMethod = mid;
   580 
   581     ctx->hidden.androidio.position = 0;
   582 
   583     if (false) {
   584 failure:
   585         result = -1;
   586 
   587         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   588 
   589         if(ctx->hidden.androidio.inputStreamRef != NULL) {
   590             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   591         }
   592 
   593         if(ctx->hidden.androidio.readableByteChannelRef != NULL) {
   594             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
   595         }
   596 
   597     }
   598 
   599     return result;
   600 }
   601 
   602 extern "C" int Android_JNI_FileOpen(SDL_RWops* ctx,
   603         const char* fileName, const char*)
   604 {
   605     LocalReferenceHolder refs(__FUNCTION__);
   606     JNIEnv *mEnv = Android_JNI_GetEnv();
   607 
   608     if (!refs.init(mEnv)) {
   609         return -1;
   610     }
   611 
   612     if (!ctx) {
   613         return -1;
   614     }
   615 
   616     jstring fileNameJString = mEnv->NewStringUTF(fileName);
   617     ctx->hidden.androidio.fileNameRef = mEnv->NewGlobalRef(fileNameJString);
   618     ctx->hidden.androidio.inputStreamRef = NULL;
   619     ctx->hidden.androidio.readableByteChannelRef = NULL;
   620     ctx->hidden.androidio.readMethod = NULL;
   621 
   622     return Android_JNI_FileOpen(ctx);
   623 }
   624 
   625 extern "C" size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
   626         size_t size, size_t maxnum)
   627 {
   628     LocalReferenceHolder refs(__FUNCTION__);
   629     jlong bytesRemaining = (jlong) (size * maxnum);
   630     jlong bytesMax = (jlong) (ctx->hidden.androidio.size -  ctx->hidden.androidio.position);
   631     int bytesRead = 0;
   632 
   633     /* Don't read more bytes than those that remain in the file, otherwise we get an exception */
   634     if (bytesRemaining >  bytesMax) bytesRemaining = bytesMax;
   635 
   636     JNIEnv *mEnv = Android_JNI_GetEnv();
   637     if (!refs.init(mEnv)) {
   638         return -1;
   639     }
   640 
   641     jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannelRef;
   642     jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
   643     jobject byteBuffer = mEnv->NewDirectByteBuffer(buffer, bytesRemaining);
   644 
   645     while (bytesRemaining > 0) {
   646         // result = readableByteChannel.read(...);
   647         int result = mEnv->CallIntMethod(readableByteChannel, readMethod, byteBuffer);
   648 
   649         if (Android_JNI_ExceptionOccurred()) {
   650             return 0;
   651         }
   652 
   653         if (result < 0) {
   654             break;
   655         }
   656 
   657         bytesRemaining -= result;
   658         bytesRead += result;
   659         ctx->hidden.androidio.position += result;
   660     }
   661 
   662     return bytesRead / size;
   663 }
   664 
   665 extern "C" size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
   666         size_t size, size_t num)
   667 {
   668     SDL_SetError("Cannot write to Android package filesystem");
   669     return 0;
   670 }
   671 
   672 static int Android_JNI_FileClose(SDL_RWops* ctx, bool release)
   673 {
   674     LocalReferenceHolder refs(__FUNCTION__);
   675     int result = 0;
   676     JNIEnv *mEnv = Android_JNI_GetEnv();
   677 
   678     if (!refs.init(mEnv)) {
   679         SDL_SetError("Failed to allocate enough JVM local references");
   680         return -1;
   681     }
   682 
   683     if (ctx) {
   684         if (release) {
   685             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   686         }
   687 
   688         jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
   689 
   690         // inputStream.close();
   691         jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   692                 "close", "()V");
   693         mEnv->CallVoidMethod(inputStream, mid);
   694         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   695         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
   696         if (Android_JNI_ExceptionOccurred()) {
   697             result = -1;
   698         }
   699 
   700         if (release) {
   701             SDL_FreeRW(ctx);
   702         }
   703     }
   704 
   705     return result;
   706 }
   707 
   708 
   709 extern "C" Sint64 Android_JNI_FileSize(SDL_RWops* ctx)
   710 {
   711     return ctx->hidden.androidio.size;
   712 }
   713 
   714 extern "C" Sint64 Android_JNI_FileSeek(SDL_RWops* ctx, Sint64 offset, int whence)
   715 {
   716     Sint64 newPosition;
   717 
   718     switch (whence) {
   719         case RW_SEEK_SET:
   720             newPosition = offset;
   721             break;
   722         case RW_SEEK_CUR:
   723             newPosition = ctx->hidden.androidio.position + offset;
   724             break;
   725         case RW_SEEK_END:
   726             newPosition = ctx->hidden.androidio.size + offset;
   727             break;
   728         default:
   729             SDL_SetError("Unknown value for 'whence'");
   730             return -1;
   731     }
   732 
   733     /* Validate the new position */
   734     if (newPosition < 0) {
   735         SDL_Error(SDL_EFSEEK);
   736         return -1;
   737     }
   738     if (newPosition > ctx->hidden.androidio.size) {
   739         newPosition = ctx->hidden.androidio.size;
   740     }
   741 
   742     Sint64 movement = newPosition - ctx->hidden.androidio.position;
   743     if (movement > 0) {
   744         unsigned char buffer[4096];
   745 
   746         // The easy case where we're seeking forwards
   747         while (movement > 0) {
   748             Sint64 amount = sizeof (buffer);
   749             if (amount > movement) {
   750                 amount = movement;
   751             }
   752             size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount);
   753             if (result <= 0) {
   754                 // Failed to read/skip the required amount, so fail
   755                 return -1;
   756             }
   757 
   758             movement -= result;
   759         }
   760 
   761     } else if (movement < 0) {
   762         // We can't seek backwards so we have to reopen the file and seek
   763         // forwards which obviously isn't very efficient
   764         Android_JNI_FileClose(ctx, false);
   765         Android_JNI_FileOpen(ctx);
   766         Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
   767     }
   768 
   769     return ctx->hidden.androidio.position;
   770 }
   771 
   772 extern "C" int Android_JNI_FileClose(SDL_RWops* ctx)
   773 {
   774     return Android_JNI_FileClose(ctx, true);
   775 }
   776 
   777 // returns a new global reference which needs to be released later
   778 static jobject Android_JNI_GetSystemServiceObject(const char* name)
   779 {
   780     LocalReferenceHolder refs(__FUNCTION__);
   781     JNIEnv* env = Android_JNI_GetEnv();
   782     if (!refs.init(env)) {
   783         return NULL;
   784     }
   785 
   786     jstring service = env->NewStringUTF(name);
   787 
   788     jmethodID mid;
   789 
   790     mid = env->GetStaticMethodID(mActivityClass, "getContext", "()Landroid/content/Context;");
   791     jobject context = env->CallStaticObjectMethod(mActivityClass, mid);
   792 
   793     mid = env->GetMethodID(mActivityClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
   794     jobject manager = env->CallObjectMethod(context, mid, service);
   795 
   796     env->DeleteLocalRef(service);
   797 
   798     return manager ? env->NewGlobalRef(manager) : NULL;
   799 }
   800 
   801 #define SETUP_CLIPBOARD(error) \
   802     LocalReferenceHolder refs(__FUNCTION__); \
   803     JNIEnv* env = Android_JNI_GetEnv(); \
   804     if (!refs.init(env)) { \
   805         return error; \
   806     } \
   807     jobject clipboard = Android_JNI_GetSystemServiceObject("clipboard"); \
   808     if (!clipboard) { \
   809         return error; \
   810     }
   811 
   812 extern "C" int Android_JNI_SetClipboardText(const char* text)
   813 {
   814     SETUP_CLIPBOARD(-1)
   815 
   816     jmethodID mid = env->GetMethodID(env->GetObjectClass(clipboard), "setText", "(Ljava/lang/CharSequence;)V");
   817     jstring string = env->NewStringUTF(text);
   818     env->CallVoidMethod(clipboard, mid, string);
   819     env->DeleteGlobalRef(clipboard);
   820     env->DeleteLocalRef(string);
   821     return 0;
   822 }
   823 
   824 extern "C" char* Android_JNI_GetClipboardText()
   825 {
   826     SETUP_CLIPBOARD(SDL_strdup(""))
   827 
   828     jmethodID mid = env->GetMethodID(env->GetObjectClass(clipboard), "getText", "()Ljava/lang/CharSequence;");
   829     jobject sequence = env->CallObjectMethod(clipboard, mid);
   830     env->DeleteGlobalRef(clipboard);
   831     if (sequence) {
   832         mid = env->GetMethodID(env->GetObjectClass(sequence), "toString", "()Ljava/lang/String;");
   833         jstring string = reinterpret_cast<jstring>(env->CallObjectMethod(sequence, mid));
   834         const char* utf = env->GetStringUTFChars(string, 0);
   835         if (utf) {
   836             char* text = SDL_strdup(utf);
   837             env->ReleaseStringUTFChars(string, utf);
   838             return text;
   839         }
   840     }
   841     return SDL_strdup("");
   842 }
   843 
   844 extern "C" SDL_bool Android_JNI_HasClipboardText()
   845 {
   846     SETUP_CLIPBOARD(SDL_FALSE)
   847 
   848     jmethodID mid = env->GetMethodID(env->GetObjectClass(clipboard), "hasText", "()Z");
   849     jboolean has = env->CallBooleanMethod(clipboard, mid);
   850     env->DeleteGlobalRef(clipboard);
   851     return has ? SDL_TRUE : SDL_FALSE;
   852 }
   853 
   854 
   855 // returns 0 on success or -1 on error (others undefined then)
   856 // returns truthy or falsy value in plugged, charged and battery
   857 // returns the value in seconds and percent or -1 if not available
   858 extern "C" int Android_JNI_GetPowerInfo(int* plugged, int* charged, int* battery, int* seconds, int* percent)
   859 {
   860     LocalReferenceHolder refs(__FUNCTION__);
   861     JNIEnv* env = Android_JNI_GetEnv();
   862     if (!refs.init(env)) {
   863         return -1;
   864     }
   865 
   866     jmethodID mid;
   867 
   868     mid = env->GetStaticMethodID(mActivityClass, "getContext", "()Landroid/content/Context;");
   869     jobject context = env->CallStaticObjectMethod(mActivityClass, mid);
   870 
   871     jstring action = env->NewStringUTF("android.intent.action.BATTERY_CHANGED");
   872 
   873     jclass cls = env->FindClass("android/content/IntentFilter");
   874 
   875     mid = env->GetMethodID(cls, "<init>", "(Ljava/lang/String;)V");
   876     jobject filter = env->NewObject(cls, mid, action);
   877 
   878     env->DeleteLocalRef(action);
   879 
   880     mid = env->GetMethodID(mActivityClass, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;");
   881     jobject intent = env->CallObjectMethod(context, mid, NULL, filter);
   882 
   883     env->DeleteLocalRef(filter);
   884 
   885     cls = env->GetObjectClass(intent);
   886 
   887     jstring iname;
   888     jmethodID imid = env->GetMethodID(cls, "getIntExtra", "(Ljava/lang/String;I)I");
   889 
   890 #define GET_INT_EXTRA(var, key) \
   891     iname = env->NewStringUTF(key); \
   892     int var = env->CallIntMethod(intent, imid, iname, -1); \
   893     env->DeleteLocalRef(iname);
   894 
   895     jstring bname;
   896     jmethodID bmid = env->GetMethodID(cls, "getBooleanExtra", "(Ljava/lang/String;Z)Z");
   897 
   898 #define GET_BOOL_EXTRA(var, key) \
   899     bname = env->NewStringUTF(key); \
   900     int var = env->CallBooleanMethod(intent, bmid, bname, JNI_FALSE); \
   901     env->DeleteLocalRef(bname);
   902 
   903     if (plugged) {
   904         GET_INT_EXTRA(plug, "plugged") // == BatteryManager.EXTRA_PLUGGED (API 5)
   905         if (plug == -1) {
   906             return -1;
   907         }
   908         // 1 == BatteryManager.BATTERY_PLUGGED_AC
   909         // 2 == BatteryManager.BATTERY_PLUGGED_USB
   910         *plugged = (0 < plug) ? 1 : 0;
   911     }
   912 
   913     if (charged) {
   914         GET_INT_EXTRA(status, "status") // == BatteryManager.EXTRA_STATUS (API 5)
   915         if (status == -1) {
   916             return -1;
   917         }
   918         // 5 == BatteryManager.BATTERY_STATUS_FULL
   919         *charged = (status == 5) ? 1 : 0;
   920     }
   921 
   922     if (battery) {
   923         GET_BOOL_EXTRA(present, "present") // == BatteryManager.EXTRA_PRESENT (API 5)
   924         *battery = present ? 1 : 0;
   925     }
   926 
   927     if (seconds) {
   928         *seconds = -1; // not possible
   929     }
   930 
   931     if (percent) {
   932         GET_INT_EXTRA(level, "level") // == BatteryManager.EXTRA_LEVEL (API 5)
   933         GET_INT_EXTRA(scale, "scale") // == BatteryManager.EXTRA_SCALE (API 5)
   934         if ((level == -1) || (scale == -1)) {
   935             return -1;
   936         }
   937         *percent = level * 100 / scale;
   938     }
   939 
   940     env->DeleteLocalRef(intent);
   941 
   942     return 0;
   943 }
   944 
   945 // sends message to be handled on the UI event dispatch thread
   946 extern "C" int Android_JNI_SendMessage(int command, int param)
   947 {
   948     JNIEnv *env = Android_JNI_GetEnv();
   949     if (!env) {
   950         return -1;
   951     }
   952     jmethodID mid = env->GetStaticMethodID(mActivityClass, "sendMessage", "(II)V");
   953     if (!mid) {
   954         return -1;
   955     }
   956     env->CallStaticVoidMethod(mActivityClass, mid, command, param);
   957     return 0;
   958 }
   959 
   960 extern "C" int Android_JNI_ShowTextInput(SDL_Rect *inputRect)
   961 {
   962     JNIEnv *env = Android_JNI_GetEnv();
   963     if (!env) {
   964         return -1;
   965     }
   966 
   967     jmethodID mid = env->GetStaticMethodID(mActivityClass, "showTextInput", "(IIII)V");
   968     if (!mid) {
   969         return -1;
   970     }
   971     env->CallStaticVoidMethod( mActivityClass, mid,
   972                                inputRect->x,
   973                                inputRect->y,
   974                                inputRect->w,
   975                                inputRect->h );
   976     return 0;
   977 }
   978 
   979 /*extern "C" int Android_JNI_HideTextInput()
   980 {
   981     JNIEnv *env = Android_JNI_GetEnv();
   982     if (!env) {
   983         return -1;
   984     }
   985 
   986     jmethodID mid = env->GetStaticMethodID(mActivityClass, "hideTextInput", "()V");
   987     if (!mid) {
   988         return -1;
   989     }
   990     env->CallStaticVoidMethod(mActivityClass, mid);
   991     return 0;
   992 }*/
   993 
   994 //////////////////////////////////////////////////////////////////////////////
   995 //
   996 // Functions exposed to SDL applications in SDL_system.h
   997 //
   998 
   999 extern "C" void *SDL_AndroidGetJNIEnv()
  1000 {
  1001     return Android_JNI_GetEnv();
  1002 }
  1003 
  1004 extern "C" void *SDL_AndroidGetActivity()
  1005 {
  1006     LocalReferenceHolder refs(__FUNCTION__);
  1007     jmethodID mid;
  1008 
  1009     JNIEnv *env = Android_JNI_GetEnv();
  1010     if (!refs.init(env)) {
  1011         return NULL;
  1012     }
  1013 
  1014     // return SDLActivity.getContext();
  1015     mid = env->GetStaticMethodID(mActivityClass,
  1016             "getContext","()Landroid/content/Context;");
  1017     return env->CallStaticObjectMethod(mActivityClass, mid);
  1018 }
  1019 
  1020 extern "C" const char * SDL_AndroidGetInternalStoragePath()
  1021 {
  1022     static char *s_AndroidInternalFilesPath = NULL;
  1023 
  1024     if (!s_AndroidInternalFilesPath) {
  1025         LocalReferenceHolder refs(__FUNCTION__);
  1026         jmethodID mid;
  1027         jobject context;
  1028         jobject fileObject;
  1029         jstring pathString;
  1030         const char *path;
  1031 
  1032         JNIEnv *env = Android_JNI_GetEnv();
  1033         if (!refs.init(env)) {
  1034             return NULL;
  1035         }
  1036 
  1037         // context = SDLActivity.getContext();
  1038         mid = env->GetStaticMethodID(mActivityClass,
  1039                 "getContext","()Landroid/content/Context;");
  1040         context = env->CallStaticObjectMethod(mActivityClass, mid);
  1041 
  1042         // fileObj = context.getFilesDir();
  1043         mid = env->GetMethodID(env->GetObjectClass(context),
  1044                 "getFilesDir", "()Ljava/io/File;");
  1045         fileObject = env->CallObjectMethod(context, mid);
  1046         if (!fileObject) {
  1047             SDL_SetError("Couldn't get internal directory");
  1048             return NULL;
  1049         }
  1050 
  1051         // path = fileObject.getAbsolutePath();
  1052         mid = env->GetMethodID(env->GetObjectClass(fileObject),
  1053                 "getAbsolutePath", "()Ljava/lang/String;");
  1054         pathString = (jstring)env->CallObjectMethod(fileObject, mid);
  1055 
  1056         path = env->GetStringUTFChars(pathString, NULL);
  1057         s_AndroidInternalFilesPath = SDL_strdup(path);
  1058         env->ReleaseStringUTFChars(pathString, path);
  1059     }
  1060     return s_AndroidInternalFilesPath;
  1061 }
  1062 
  1063 extern "C" int SDL_AndroidGetExternalStorageState()
  1064 {
  1065     LocalReferenceHolder refs(__FUNCTION__);
  1066     jmethodID mid;
  1067     jclass cls;
  1068     jstring stateString;
  1069     const char *state;
  1070     int stateFlags;
  1071 
  1072     JNIEnv *env = Android_JNI_GetEnv();
  1073     if (!refs.init(env)) {
  1074         return 0;
  1075     }
  1076 
  1077     cls = env->FindClass("android/os/Environment");
  1078     mid = env->GetStaticMethodID(cls,
  1079             "getExternalStorageState", "()Ljava/lang/String;");
  1080     stateString = (jstring)env->CallStaticObjectMethod(cls, mid);
  1081 
  1082     state = env->GetStringUTFChars(stateString, NULL);
  1083 
  1084     // Print an info message so people debugging know the storage state
  1085     __android_log_print(ANDROID_LOG_INFO, "SDL", "external storage state: %s", state);
  1086 
  1087     if (SDL_strcmp(state, "mounted") == 0) {
  1088         stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ |
  1089                      SDL_ANDROID_EXTERNAL_STORAGE_WRITE;
  1090     } else if (SDL_strcmp(state, "mounted_ro") == 0) {
  1091         stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ;
  1092     } else {
  1093         stateFlags = 0;
  1094     }
  1095     env->ReleaseStringUTFChars(stateString, state);
  1096 
  1097     return stateFlags;
  1098 }
  1099 
  1100 extern "C" const char * SDL_AndroidGetExternalStoragePath()
  1101 {
  1102     static char *s_AndroidExternalFilesPath = NULL;
  1103 
  1104     if (!s_AndroidExternalFilesPath) {
  1105         LocalReferenceHolder refs(__FUNCTION__);
  1106         jmethodID mid;
  1107         jobject context;
  1108         jobject fileObject;
  1109         jstring pathString;
  1110         const char *path;
  1111 
  1112         JNIEnv *env = Android_JNI_GetEnv();
  1113         if (!refs.init(env)) {
  1114             return NULL;
  1115         }
  1116 
  1117         // context = SDLActivity.getContext();
  1118         mid = env->GetStaticMethodID(mActivityClass,
  1119                 "getContext","()Landroid/content/Context;");
  1120         context = env->CallStaticObjectMethod(mActivityClass, mid);
  1121 
  1122         // fileObj = context.getExternalFilesDir();
  1123         mid = env->GetMethodID(env->GetObjectClass(context),
  1124                 "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;");
  1125         fileObject = env->CallObjectMethod(context, mid, NULL);
  1126         if (!fileObject) {
  1127             SDL_SetError("Couldn't get external directory");
  1128             return NULL;
  1129         }
  1130 
  1131         // path = fileObject.getAbsolutePath();
  1132         mid = env->GetMethodID(env->GetObjectClass(fileObject),
  1133                 "getAbsolutePath", "()Ljava/lang/String;");
  1134         pathString = (jstring)env->CallObjectMethod(fileObject, mid);
  1135 
  1136         path = env->GetStringUTFChars(pathString, NULL);
  1137         s_AndroidExternalFilesPath = SDL_strdup(path);
  1138         env->ReleaseStringUTFChars(pathString, path);
  1139     }
  1140     return s_AndroidExternalFilesPath;
  1141 }
  1142 
  1143 #endif /* __ANDROID__ */
  1144 
  1145 /* vi: set ts=4 sw=4 expandtab: */