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