src/core/android/SDL_android.cpp
author Gabriel Jacobo <gabomdq@gmail.com>
Mon, 09 Jul 2012 18:08:06 -0300
changeset 6354 17840f487124
parent 6335 fbb84f5b985f
child 6377 3d868ca4782f
permissions -rwxr-xr-x
Fixes #1422, removes global JNI Env, uses per thread copies, adds thread auto detaching.
     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_android.h"
    28 
    29 extern "C" {
    30 #include "../../events/SDL_events_c.h"
    31 #include "../../video/android/SDL_androidkeyboard.h"
    32 #include "../../video/android/SDL_androidtouch.h"
    33 #include "../../video/android/SDL_androidvideo.h"
    34 
    35 #include <android/log.h>
    36 #include <pthread.h>
    37 #define LOG_TAG "SDL_android"
    38 //#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
    39 //#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
    40 #define LOGI(...) do {} while (false)
    41 #define LOGE(...) do {} while (false)
    42 
    43 
    44 /* Implemented in audio/android/SDL_androidaudio.c */
    45 extern void Android_RunAudioThread();
    46 } // C
    47 
    48 /*******************************************************************************
    49  This file links the Java side of Android with libsdl
    50 *******************************************************************************/
    51 #include <jni.h>
    52 #include <android/log.h>
    53 
    54 
    55 /*******************************************************************************
    56                                Globals
    57 *******************************************************************************/
    58 static pthread_key_t mThreadKey;
    59 static JavaVM* mJavaVM;
    60 
    61 // Main activity
    62 static jclass mActivityClass;
    63 
    64 // method signatures
    65 static jmethodID midCreateGLContext;
    66 static jmethodID midFlipBuffers;
    67 static jmethodID midAudioInit;
    68 static jmethodID midAudioWriteShortBuffer;
    69 static jmethodID midAudioWriteByteBuffer;
    70 static jmethodID midAudioQuit;
    71 
    72 // Accelerometer data storage
    73 static float fLastAccelerometer[3];
    74 static bool bHasNewData;
    75 
    76 /*******************************************************************************
    77                  Functions called by JNI
    78 *******************************************************************************/
    79 
    80 // Library init
    81 extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
    82 {
    83     JNIEnv *env;
    84     mJavaVM = vm;
    85     LOGI("JNI_OnLoad called");
    86     if (mJavaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
    87         LOGE("Failed to get the environment using GetEnv()");
    88         return -1;
    89     }
    90     /*
    91      * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread
    92      * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this
    93      */
    94     if (pthread_key_create(&mThreadKey, Android_JNI_ThreadDestroyed)) {
    95         __android_log_print(ANDROID_LOG_ERROR, "SDL", "Error initializing pthread key");
    96     }
    97     else {
    98         Android_JNI_SetupThread();
    99     }
   100 
   101     return JNI_VERSION_1_4;
   102 }
   103 
   104 // Called before SDL_main() to initialize JNI bindings
   105 extern "C" void SDL_Android_Init(JNIEnv* mEnv, jclass cls)
   106 {
   107     __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init()");
   108 
   109     Android_JNI_SetupThread();
   110 
   111     mActivityClass = (jclass)mEnv->NewGlobalRef(cls);
   112 
   113     midCreateGLContext = mEnv->GetStaticMethodID(mActivityClass,
   114                                 "createGLContext","(II)Z");
   115     midFlipBuffers = mEnv->GetStaticMethodID(mActivityClass,
   116                                 "flipBuffers","()V");
   117     midAudioInit = mEnv->GetStaticMethodID(mActivityClass, 
   118                                 "audioInit", "(IZZI)Ljava/lang/Object;");
   119     midAudioWriteShortBuffer = mEnv->GetStaticMethodID(mActivityClass,
   120                                 "audioWriteShortBuffer", "([S)V");
   121     midAudioWriteByteBuffer = mEnv->GetStaticMethodID(mActivityClass,
   122                                 "audioWriteByteBuffer", "([B)V");
   123     midAudioQuit = mEnv->GetStaticMethodID(mActivityClass,
   124                                 "audioQuit", "()V");
   125 
   126     bHasNewData = false;
   127 
   128     if(!midCreateGLContext || !midFlipBuffers || !midAudioInit ||
   129        !midAudioWriteShortBuffer || !midAudioWriteByteBuffer || !midAudioQuit) {
   130         __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL: Couldn't locate Java callbacks, check that they're named and typed correctly");
   131     }
   132     __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init() finished!");
   133 }
   134 
   135 // Resize
   136 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeResize(
   137                                     JNIEnv* env, jclass jcls,
   138                                     jint width, jint height, jint format)
   139 {
   140     Android_SetScreenResolution(width, height, format);
   141 }
   142 
   143 // Keydown
   144 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyDown(
   145                                     JNIEnv* env, jclass jcls, jint keycode)
   146 {
   147     Android_OnKeyDown(keycode);
   148 }
   149 
   150 // Keyup
   151 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyUp(
   152                                     JNIEnv* env, jclass jcls, jint keycode)
   153 {
   154     Android_OnKeyUp(keycode);
   155 }
   156 
   157 // Touch
   158 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeTouch(
   159                                     JNIEnv* env, jclass jcls,
   160                                     jint touch_device_id_in, jint pointer_finger_id_in,
   161                                     jint action, jfloat x, jfloat y, jfloat p)
   162 {
   163     Android_OnTouch(touch_device_id_in, pointer_finger_id_in, action, x, y, p);
   164 }
   165 
   166 // Accelerometer
   167 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeAccel(
   168                                     JNIEnv* env, jclass jcls,
   169                                     jfloat x, jfloat y, jfloat z)
   170 {
   171     fLastAccelerometer[0] = x;
   172     fLastAccelerometer[1] = y;
   173     fLastAccelerometer[2] = z;
   174     bHasNewData = true;
   175 }
   176 
   177 // Quit
   178 extern "C" void Java_org_libsdl_app_SDLActivity_nativeQuit(
   179                                     JNIEnv* env, jclass cls)
   180 {    
   181     // Inject a SDL_QUIT event
   182     SDL_SendQuit();
   183 }
   184 
   185 // Pause
   186 extern "C" void Java_org_libsdl_app_SDLActivity_nativePause(
   187                                     JNIEnv* env, jclass cls)
   188 {
   189     if (Android_Window) {
   190         /* Signal the pause semaphore so the event loop knows to pause and (optionally) block itself */
   191         if (!SDL_SemValue(Android_PauseSem)) SDL_SemPost(Android_PauseSem);
   192         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_LOST, 0, 0);
   193         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_MINIMIZED, 0, 0);
   194     }
   195 }
   196 
   197 // Resume
   198 extern "C" void Java_org_libsdl_app_SDLActivity_nativeResume(
   199                                     JNIEnv* env, jclass cls)
   200 {
   201     if (Android_Window) {
   202         /* Signal the resume semaphore so the event loop knows to resume and restore the GL Context
   203          * We can't restore the GL Context here because it needs to be done on the SDL main thread
   204          * and this function will be called from the Java thread instead.
   205          */
   206         if (!SDL_SemValue(Android_ResumeSem)) SDL_SemPost(Android_ResumeSem);
   207         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_GAINED, 0, 0);
   208         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_RESTORED, 0, 0);
   209     }
   210 }
   211 
   212 extern "C" void Java_org_libsdl_app_SDLActivity_nativeRunAudioThread(
   213                                     JNIEnv* env, jclass cls)
   214 {
   215     /* This is the audio thread, with a different environment */
   216     Android_JNI_SetupThread();
   217 
   218     Android_RunAudioThread();
   219 }
   220 
   221 
   222 /*******************************************************************************
   223              Functions called by SDL into Java
   224 *******************************************************************************/
   225 
   226 class LocalReferenceHolder
   227 {
   228 private:
   229     static int s_active;
   230 
   231 public:
   232     static bool IsActive() {
   233         return s_active > 0;
   234     }
   235 
   236 public:
   237     LocalReferenceHolder() : m_env(NULL) { }
   238     ~LocalReferenceHolder() {
   239         if (m_env) {
   240             m_env->PopLocalFrame(NULL);
   241             --s_active;
   242         }
   243     }
   244 
   245     bool init(JNIEnv *env, jint capacity = 16) {
   246         if (env->PushLocalFrame(capacity) < 0) {
   247             SDL_SetError("Failed to allocate enough JVM local references");
   248             return false;
   249         }
   250         ++s_active;
   251         m_env = env;
   252         return true;
   253     }
   254 
   255 protected:
   256     JNIEnv *m_env;
   257 };
   258 int LocalReferenceHolder::s_active;
   259 
   260 extern "C" SDL_bool Android_JNI_CreateContext(int majorVersion, int minorVersion)
   261 {
   262     JNIEnv *mEnv = Android_JNI_GetEnv();
   263     if (mEnv->CallStaticBooleanMethod(mActivityClass, midCreateGLContext, majorVersion, minorVersion)) {
   264         return SDL_TRUE;
   265     } else {
   266         return SDL_FALSE;
   267     }
   268 }
   269 
   270 extern "C" void Android_JNI_SwapWindow()
   271 {
   272     JNIEnv *mEnv = Android_JNI_GetEnv();
   273     mEnv->CallStaticVoidMethod(mActivityClass, midFlipBuffers); 
   274 }
   275 
   276 extern "C" void Android_JNI_SetActivityTitle(const char *title)
   277 {
   278     jmethodID mid;
   279     JNIEnv *mEnv = Android_JNI_GetEnv();
   280     mid = mEnv->GetStaticMethodID(mActivityClass,"setActivityTitle","(Ljava/lang/String;)V");
   281     if (mid) {
   282         jstring jtitle = reinterpret_cast<jstring>(mEnv->NewStringUTF(title));
   283         mEnv->CallStaticVoidMethod(mActivityClass, mid, jtitle);
   284         mEnv->DeleteLocalRef(jtitle);
   285     }
   286 }
   287 
   288 extern "C" SDL_bool Android_JNI_GetAccelerometerValues(float values[3])
   289 {
   290     int i;
   291     SDL_bool retval = SDL_FALSE;
   292 
   293     if (bHasNewData) {
   294         for (i = 0; i < 3; ++i) {
   295             values[i] = fLastAccelerometer[i];
   296         }
   297         bHasNewData = false;
   298         retval = SDL_TRUE;
   299     }
   300 
   301     return retval;
   302 }
   303 
   304 static void Android_JNI_ThreadDestroyed(void* value) {
   305     /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */
   306     JNIEnv *env = (JNIEnv*) value;
   307     if (env != NULL) {
   308         mJavaVM->DetachCurrentThread();
   309         pthread_setspecific(mThreadKey, NULL);
   310     }
   311 }
   312 
   313 JNIEnv* Android_JNI_GetEnv(void) {
   314     /* From http://developer.android.com/guide/practices/jni.html
   315      * All threads are Linux threads, scheduled by the kernel.
   316      * They're usually started from managed code (using Thread.start), but they can also be created elsewhere and then
   317      * attached to the JavaVM. For example, a thread started with pthread_create can be attached with the
   318      * JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a thread is attached, it has no JNIEnv,
   319      * and cannot make JNI calls.
   320      * Attaching a natively-created thread causes a java.lang.Thread object to be constructed and added to the "main"
   321      * ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread on an already-attached thread
   322      * is a no-op.
   323      * Note: You can call this function any number of times for the same thread, there's no harm in it
   324      */
   325 
   326     JNIEnv *env;
   327     int status = mJavaVM->AttachCurrentThread(&env, NULL);
   328     if(status < 0) {
   329         LOGE("failed to attach current thread");
   330         return 0;
   331     }
   332 
   333     return env;
   334 }
   335 
   336 int Android_JNI_SetupThread(void) {
   337     /* From http://developer.android.com/guide/practices/jni.html
   338      * Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward,
   339      * in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be
   340      * called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific
   341      * to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.)
   342      * Note: The destructor is not called unless the stored value is != NULL
   343      * Note: You can call this function any number of times for the same thread, there's no harm in it
   344      *       (except for some lost CPU cycles)
   345      */
   346     JNIEnv *env = Android_JNI_GetEnv();
   347     pthread_setspecific(mThreadKey, (void*) env);
   348     return 1;
   349 }
   350 
   351 //
   352 // Audio support
   353 //
   354 static jboolean audioBuffer16Bit = JNI_FALSE;
   355 static jboolean audioBufferStereo = JNI_FALSE;
   356 static jobject audioBuffer = NULL;
   357 static void* audioBufferPinned = NULL;
   358 
   359 extern "C" int Android_JNI_OpenAudioDevice(int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
   360 {
   361     int audioBufferFrames;
   362 
   363     int status;
   364     JNIEnv *env = Android_JNI_GetEnv();
   365 
   366     if (!env) {
   367         LOGE("callback_handler: failed to attach current thread");
   368     }
   369     Android_JNI_SetupThread();
   370 
   371     
   372     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device");
   373     audioBuffer16Bit = is16Bit;
   374     audioBufferStereo = channelCount > 1;
   375 
   376     audioBuffer = env->CallStaticObjectMethod(mActivityClass, midAudioInit, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames);
   377 
   378     if (audioBuffer == NULL) {
   379         __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: didn't get back a good audio buffer!");
   380         return 0;
   381     }
   382     audioBuffer = env->NewGlobalRef(audioBuffer);
   383 
   384     jboolean isCopy = JNI_FALSE;
   385     if (audioBuffer16Bit) {
   386         audioBufferPinned = env->GetShortArrayElements((jshortArray)audioBuffer, &isCopy);
   387         audioBufferFrames = env->GetArrayLength((jshortArray)audioBuffer);
   388     } else {
   389         audioBufferPinned = env->GetByteArrayElements((jbyteArray)audioBuffer, &isCopy);
   390         audioBufferFrames = env->GetArrayLength((jbyteArray)audioBuffer);
   391     }
   392     if (audioBufferStereo) {
   393         audioBufferFrames /= 2;
   394     }
   395  
   396     return audioBufferFrames;
   397 }
   398 
   399 extern "C" void * Android_JNI_GetAudioBuffer()
   400 {
   401     return audioBufferPinned;
   402 }
   403 
   404 extern "C" void Android_JNI_WriteAudioBuffer()
   405 {
   406     JNIEnv *mAudioEnv = Android_JNI_GetEnv();
   407 
   408     if (audioBuffer16Bit) {
   409         mAudioEnv->ReleaseShortArrayElements((jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
   410         mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
   411     } else {
   412         mAudioEnv->ReleaseByteArrayElements((jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT);
   413         mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer);
   414     }
   415 
   416     /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
   417 }
   418 
   419 extern "C" void Android_JNI_CloseAudioDevice()
   420 {
   421     int status;
   422     JNIEnv *env = Android_JNI_GetEnv();
   423 
   424     env->CallStaticVoidMethod(mActivityClass, midAudioQuit); 
   425 
   426     if (audioBuffer) {
   427         env->DeleteGlobalRef(audioBuffer);
   428         audioBuffer = NULL;
   429         audioBufferPinned = NULL;
   430     }
   431 }
   432 
   433 // Test for an exception and call SDL_SetError with its detail if one occurs
   434 static bool Android_JNI_ExceptionOccurred()
   435 {
   436     SDL_assert(LocalReferenceHolder::IsActive());
   437     JNIEnv *mEnv = Android_JNI_GetEnv();
   438 
   439     jthrowable exception = mEnv->ExceptionOccurred();
   440     if (exception != NULL) {
   441         jmethodID mid;
   442 
   443         // Until this happens most JNI operations have undefined behaviour
   444         mEnv->ExceptionClear();
   445 
   446         jclass exceptionClass = mEnv->GetObjectClass(exception);
   447         jclass classClass = mEnv->FindClass("java/lang/Class");
   448 
   449         mid = mEnv->GetMethodID(classClass, "getName", "()Ljava/lang/String;");
   450         jstring exceptionName = (jstring)mEnv->CallObjectMethod(exceptionClass, mid);
   451         const char* exceptionNameUTF8 = mEnv->GetStringUTFChars(exceptionName, 0);
   452 
   453         mid = mEnv->GetMethodID(exceptionClass, "getMessage", "()Ljava/lang/String;");
   454         jstring exceptionMessage = (jstring)mEnv->CallObjectMethod(exception, mid);
   455 
   456         if (exceptionMessage != NULL) {
   457             const char* exceptionMessageUTF8 = mEnv->GetStringUTFChars(
   458                     exceptionMessage, 0);
   459             SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
   460             mEnv->ReleaseStringUTFChars(exceptionMessage, exceptionMessageUTF8);
   461         } else {
   462             SDL_SetError("%s", exceptionNameUTF8);
   463         }
   464 
   465         mEnv->ReleaseStringUTFChars(exceptionName, exceptionNameUTF8);
   466 
   467         return true;
   468     }
   469 
   470     return false;
   471 }
   472 
   473 static int Android_JNI_FileOpen(SDL_RWops* ctx)
   474 {
   475     LocalReferenceHolder refs;
   476     int result = 0;
   477 
   478     jmethodID mid;
   479     jobject context;
   480     jobject assetManager;
   481     jobject inputStream;
   482     jclass channels;
   483     jobject readableByteChannel;
   484     jstring fileNameJString;
   485 
   486     JNIEnv *mEnv = Android_JNI_GetEnv();
   487     if (!refs.init(mEnv)) {
   488         goto failure;
   489     }
   490 
   491     fileNameJString = (jstring)ctx->hidden.androidio.fileNameRef;
   492 
   493     // context = SDLActivity.getContext();
   494     mid = mEnv->GetStaticMethodID(mActivityClass,
   495             "getContext","()Landroid/content/Context;");
   496     context = mEnv->CallStaticObjectMethod(mActivityClass, mid);
   497 
   498     // assetManager = context.getAssets();
   499     mid = mEnv->GetMethodID(mEnv->GetObjectClass(context),
   500             "getAssets", "()Landroid/content/res/AssetManager;");
   501     assetManager = mEnv->CallObjectMethod(context, mid);
   502 
   503     // inputStream = assetManager.open(<filename>);
   504     mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager),
   505             "open", "(Ljava/lang/String;)Ljava/io/InputStream;");
   506     inputStream = mEnv->CallObjectMethod(assetManager, mid, fileNameJString);
   507     if (Android_JNI_ExceptionOccurred()) {
   508         goto failure;
   509     }
   510 
   511     ctx->hidden.androidio.inputStreamRef = mEnv->NewGlobalRef(inputStream);
   512 
   513     // Despite all the visible documentation on [Asset]InputStream claiming
   514     // that the .available() method is not guaranteed to return the entire file
   515     // size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
   516     // android/apis/content/ReadAsset.java imply that Android's
   517     // AssetInputStream.available() /will/ always return the total file size
   518 
   519     // size = inputStream.available();
   520     mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   521             "available", "()I");
   522     ctx->hidden.androidio.size = mEnv->CallIntMethod(inputStream, mid);
   523     if (Android_JNI_ExceptionOccurred()) {
   524         goto failure;
   525     }
   526 
   527     // readableByteChannel = Channels.newChannel(inputStream);
   528     channels = mEnv->FindClass("java/nio/channels/Channels");
   529     mid = mEnv->GetStaticMethodID(channels,
   530             "newChannel",
   531             "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
   532     readableByteChannel = mEnv->CallStaticObjectMethod(
   533             channels, mid, inputStream);
   534     if (Android_JNI_ExceptionOccurred()) {
   535         goto failure;
   536     }
   537 
   538     ctx->hidden.androidio.readableByteChannelRef =
   539         mEnv->NewGlobalRef(readableByteChannel);
   540 
   541     // Store .read id for reading purposes
   542     mid = mEnv->GetMethodID(mEnv->GetObjectClass(readableByteChannel),
   543             "read", "(Ljava/nio/ByteBuffer;)I");
   544     ctx->hidden.androidio.readMethod = mid;
   545 
   546     ctx->hidden.androidio.position = 0;
   547 
   548     if (false) {
   549 failure:
   550         result = -1;
   551 
   552         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   553 
   554         if(ctx->hidden.androidio.inputStreamRef != NULL) {
   555             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   556         }
   557 
   558         if(ctx->hidden.androidio.readableByteChannelRef != NULL) {
   559             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
   560         }
   561 
   562     }
   563 
   564     return result;
   565 }
   566 
   567 extern "C" int Android_JNI_FileOpen(SDL_RWops* ctx,
   568         const char* fileName, const char*)
   569 {
   570     LocalReferenceHolder refs;
   571     JNIEnv *mEnv = Android_JNI_GetEnv();
   572 
   573     if (!refs.init(mEnv)) {
   574         return -1;
   575     }
   576 
   577     if (!ctx) {
   578         return -1;
   579     }
   580 
   581     jstring fileNameJString = mEnv->NewStringUTF(fileName);
   582     ctx->hidden.androidio.fileNameRef = mEnv->NewGlobalRef(fileNameJString);
   583     ctx->hidden.androidio.inputStreamRef = NULL;
   584     ctx->hidden.androidio.readableByteChannelRef = NULL;
   585     ctx->hidden.androidio.readMethod = NULL;
   586 
   587     return Android_JNI_FileOpen(ctx);
   588 }
   589 
   590 extern "C" size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
   591         size_t size, size_t maxnum)
   592 {
   593     LocalReferenceHolder refs;
   594     int bytesRemaining = size * maxnum;
   595     int bytesRead = 0;
   596 
   597     JNIEnv *mEnv = Android_JNI_GetEnv();
   598     if (!refs.init(mEnv)) {
   599         return -1;
   600     }
   601 
   602     jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannelRef;
   603     jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
   604     jobject byteBuffer = mEnv->NewDirectByteBuffer(buffer, bytesRemaining);
   605 
   606     while (bytesRemaining > 0) {
   607         // result = readableByteChannel.read(...);
   608         int result = mEnv->CallIntMethod(readableByteChannel, readMethod, byteBuffer);
   609 
   610         if (Android_JNI_ExceptionOccurred()) {
   611             return 0;
   612         }
   613 
   614         if (result < 0) {
   615             break;
   616         }
   617 
   618         bytesRemaining -= result;
   619         bytesRead += result;
   620         ctx->hidden.androidio.position += result;
   621     }
   622 
   623     return bytesRead / size;
   624 }
   625 
   626 extern "C" size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
   627         size_t size, size_t num)
   628 {
   629     SDL_SetError("Cannot write to Android package filesystem");
   630     return 0;
   631 }
   632 
   633 static int Android_JNI_FileClose(SDL_RWops* ctx, bool release)
   634 {
   635     LocalReferenceHolder refs;
   636     int result = 0;
   637     JNIEnv *mEnv = Android_JNI_GetEnv();
   638 
   639     if (!refs.init(mEnv)) {
   640         SDL_SetError("Failed to allocate enough JVM local references");
   641         return -1;
   642     }
   643 
   644     if (ctx) {
   645         if (release) {
   646             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   647         }
   648 
   649         jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
   650 
   651         // inputStream.close();
   652         jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   653                 "close", "()V");
   654         mEnv->CallVoidMethod(inputStream, mid);
   655         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   656         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
   657         if (Android_JNI_ExceptionOccurred()) {
   658             result = -1;
   659         }
   660 
   661         if (release) {
   662             SDL_FreeRW(ctx);
   663         }
   664     }
   665 
   666     return result;
   667 }
   668 
   669 
   670 extern "C" long Android_JNI_FileSeek(SDL_RWops* ctx, long offset, int whence)
   671 {
   672     long newPosition;
   673 
   674     switch (whence) {
   675         case RW_SEEK_SET:
   676             newPosition = offset;
   677             break;
   678         case RW_SEEK_CUR:
   679             newPosition = ctx->hidden.androidio.position + offset;
   680             break;
   681         case RW_SEEK_END:
   682             newPosition = ctx->hidden.androidio.size + offset;
   683             break;
   684         default:
   685             SDL_SetError("Unknown value for 'whence'");
   686             return -1;
   687     }
   688     if (newPosition < 0) {
   689         newPosition = 0;
   690     }
   691     if (newPosition > ctx->hidden.androidio.size) {
   692         newPosition = ctx->hidden.androidio.size;
   693     }
   694 
   695     long movement = newPosition - ctx->hidden.androidio.position;
   696     jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
   697 
   698     if (movement > 0) {
   699         unsigned char buffer[1024];
   700 
   701         // The easy case where we're seeking forwards
   702         while (movement > 0) {
   703             long amount = (long) sizeof (buffer);
   704             if (amount > movement) {
   705                 amount = movement;
   706             }
   707             size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount);
   708 
   709             if (result <= 0) {
   710                 // Failed to read/skip the required amount, so fail
   711                 return -1;
   712             }
   713 
   714             movement -= result;
   715         }
   716     } else if (movement < 0) {
   717         // We can't seek backwards so we have to reopen the file and seek
   718         // forwards which obviously isn't very efficient
   719         Android_JNI_FileClose(ctx, false);
   720         Android_JNI_FileOpen(ctx);
   721         Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
   722     }
   723 
   724     ctx->hidden.androidio.position = newPosition;
   725 
   726     return ctx->hidden.androidio.position;
   727 }
   728 
   729 extern "C" int Android_JNI_FileClose(SDL_RWops* ctx)
   730 {
   731     return Android_JNI_FileClose(ctx, true);
   732 }
   733 
   734 #endif /* __ANDROID__ */
   735 
   736 /* vi: set ts=4 sw=4 expandtab: */