src/core/android/SDL_android.cpp
author Gabriel Jacobo <gabomdq@gmail.com>
Tue, 19 Jun 2012 13:57:42 -0300
changeset 6330 0fa55ca2efdd
parent 6308 263e2c049c22
child 6335 fbb84f5b985f
permissions -rwxr-xr-x
Fixes #1422, restores GL context automatically under Android
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2012 Sam Lantinga <slouken@libsdl.org>
     4 
     5   This software is provided 'as-is', without any express or implied
     6   warranty.  In no event will the authors be held liable for any damages
     7   arising from the use of this software.
     8 
     9   Permission is granted to anyone to use this software for any purpose,
    10   including commercial applications, and to alter it and redistribute it
    11   freely, subject to the following restrictions:
    12 
    13   1. The origin of this software must not be misrepresented; you must not
    14      claim that you wrote the original software. If you use this software
    15      in a product, an acknowledgment in the product documentation would be
    16      appreciated but is not required.
    17   2. Altered source versions must be plainly marked as such, and must not be
    18      misrepresented as being the original software.
    19   3. This notice may not be removed or altered from any source distribution.
    20 */
    21 #include "SDL_config.h"
    22 #include "SDL_stdinc.h"
    23 #include "SDL_assert.h"
    24 
    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 #define LOG_TAG "SDL_android"
    37 //#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
    38 //#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
    39 #define LOGI(...) do {} while (false)
    40 #define LOGE(...) do {} while (false)
    41 
    42 
    43 /* Implemented in audio/android/SDL_androidaudio.c */
    44 extern void Android_RunAudioThread();
    45 } // C
    46 
    47 /*******************************************************************************
    48  This file links the Java side of Android with libsdl
    49 *******************************************************************************/
    50 #include <jni.h>
    51 #include <android/log.h>
    52 
    53 
    54 /*******************************************************************************
    55                                Globals
    56 *******************************************************************************/
    57 static JNIEnv* mEnv = NULL;
    58 static JNIEnv* mAudioEnv = NULL;
    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     return JNI_VERSION_1_4;
    92 }
    93 
    94 // Called before SDL_main() to initialize JNI bindings
    95 extern "C" void SDL_Android_Init(JNIEnv* env, jclass cls)
    96 {
    97     __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init()");
    98 
    99     mEnv = env;
   100     mActivityClass = (jclass)env->NewGlobalRef(cls);
   101 
   102     midCreateGLContext = mEnv->GetStaticMethodID(mActivityClass,
   103                                 "createGLContext","(II)Z");
   104     midFlipBuffers = mEnv->GetStaticMethodID(mActivityClass,
   105                                 "flipBuffers","()V");
   106     midAudioInit = mEnv->GetStaticMethodID(mActivityClass, 
   107                                 "audioInit", "(IZZI)Ljava/lang/Object;");
   108     midAudioWriteShortBuffer = mEnv->GetStaticMethodID(mActivityClass,
   109                                 "audioWriteShortBuffer", "([S)V");
   110     midAudioWriteByteBuffer = mEnv->GetStaticMethodID(mActivityClass,
   111                                 "audioWriteByteBuffer", "([B)V");
   112     midAudioQuit = mEnv->GetStaticMethodID(mActivityClass,
   113                                 "audioQuit", "()V");
   114 
   115     bHasNewData = false;
   116 
   117     if(!midCreateGLContext || !midFlipBuffers || !midAudioInit ||
   118        !midAudioWriteShortBuffer || !midAudioWriteByteBuffer || !midAudioQuit) {
   119         __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL: Couldn't locate Java callbacks, check that they're named and typed correctly");
   120     }
   121     __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init() finished!");
   122 }
   123 
   124 // Resize
   125 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeResize(
   126                                     JNIEnv* env, jclass jcls,
   127                                     jint width, jint height, jint format)
   128 {
   129     Android_SetScreenResolution(width, height, format);
   130 }
   131 
   132 // Keydown
   133 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyDown(
   134                                     JNIEnv* env, jclass jcls, jint keycode)
   135 {
   136     Android_OnKeyDown(keycode);
   137 }
   138 
   139 // Keyup
   140 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyUp(
   141                                     JNIEnv* env, jclass jcls, jint keycode)
   142 {
   143     Android_OnKeyUp(keycode);
   144 }
   145 
   146 // Touch
   147 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeTouch(
   148                                     JNIEnv* env, jclass jcls,
   149                                     jint touch_device_id_in, jint pointer_finger_id_in,
   150                                     jint action, jfloat x, jfloat y, jfloat p)
   151 {
   152     Android_OnTouch(touch_device_id_in, pointer_finger_id_in, action, x, y, p);
   153 }
   154 
   155 // Accelerometer
   156 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeAccel(
   157                                     JNIEnv* env, jclass jcls,
   158                                     jfloat x, jfloat y, jfloat z)
   159 {
   160     fLastAccelerometer[0] = x;
   161     fLastAccelerometer[1] = y;
   162     fLastAccelerometer[2] = z;
   163     bHasNewData = true;
   164 }
   165 
   166 // Quit
   167 extern "C" void Java_org_libsdl_app_SDLActivity_nativeQuit(
   168                                     JNIEnv* env, jclass cls)
   169 {    
   170     // Inject a SDL_QUIT event
   171     SDL_SendQuit();
   172 }
   173 
   174 // Pause
   175 extern "C" void Java_org_libsdl_app_SDLActivity_nativePause(
   176                                     JNIEnv* env, jclass cls)
   177 {
   178     if (Android_Window) {
   179         /* Signal the pause semaphore so the event loop knows to pause and (optionally) block itself */
   180         if (!SDL_SemValue(Android_PauseSem)) SDL_SemPost(Android_PauseSem);
   181         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_LOST, 0, 0);
   182         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_MINIMIZED, 0, 0);
   183     }
   184 }
   185 
   186 // Resume
   187 extern "C" void Java_org_libsdl_app_SDLActivity_nativeResume(
   188                                     JNIEnv* env, jclass cls)
   189 {
   190     if (Android_Window) {
   191         /* Signal the resume semaphore so the event loop knows to resume and restore the GL Context
   192          * We can't restore the GL Context here because it needs to be done on the SDL main thread
   193          * and this function will be called from the Java thread instead.
   194          */
   195         if (!SDL_SemValue(Android_ResumeSem)) SDL_SemPost(Android_ResumeSem);
   196         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_GAINED, 0, 0);
   197         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_RESTORED, 0, 0);
   198     }
   199 }
   200 
   201 extern "C" void Java_org_libsdl_app_SDLActivity_nativeRunAudioThread(
   202                                     JNIEnv* env, jclass cls)
   203 {
   204     /* This is the audio thread, with a different environment */
   205     mAudioEnv = env;
   206 
   207     Android_RunAudioThread();
   208 }
   209 
   210 
   211 /*******************************************************************************
   212              Functions called by SDL into Java
   213 *******************************************************************************/
   214 
   215 class LocalReferenceHolder
   216 {
   217 private:
   218     static int s_active;
   219 
   220 public:
   221     static bool IsActive() {
   222         return s_active > 0;
   223     }
   224 
   225 public:
   226     LocalReferenceHolder() : m_env(NULL) { }
   227     ~LocalReferenceHolder() {
   228         if (m_env) {
   229             m_env->PopLocalFrame(NULL);
   230             --s_active;
   231         }
   232     }
   233 
   234     bool init(JNIEnv *env, jint capacity = 16) {
   235         if (env->PushLocalFrame(capacity) < 0) {
   236             SDL_SetError("Failed to allocate enough JVM local references");
   237             return false;
   238         }
   239         ++s_active;
   240         m_env = env;
   241         return true;
   242     }
   243 
   244 protected:
   245     JNIEnv *m_env;
   246 };
   247 int LocalReferenceHolder::s_active;
   248 
   249 extern "C" SDL_bool Android_JNI_CreateContext(int majorVersion, int minorVersion)
   250 {
   251     if (mEnv->CallStaticBooleanMethod(mActivityClass, midCreateGLContext, majorVersion, minorVersion)) {
   252         return SDL_TRUE;
   253     } else {
   254         return SDL_FALSE;
   255     }
   256 }
   257 
   258 extern "C" void Android_JNI_SwapWindow()
   259 {
   260     mEnv->CallStaticVoidMethod(mActivityClass, midFlipBuffers); 
   261 }
   262 
   263 extern "C" void Android_JNI_SetActivityTitle(const char *title)
   264 {
   265     jmethodID mid;
   266 
   267     mid = mEnv->GetStaticMethodID(mActivityClass,"setActivityTitle","(Ljava/lang/String;)V");
   268     if (mid) {
   269         jstring jtitle = reinterpret_cast<jstring>(mEnv->NewStringUTF(title));
   270         mEnv->CallStaticVoidMethod(mActivityClass, mid, jtitle);
   271         mEnv->DeleteLocalRef(jtitle);
   272     }
   273 }
   274 
   275 extern "C" SDL_bool Android_JNI_GetAccelerometerValues(float values[3])
   276 {
   277     int i;
   278     SDL_bool retval = SDL_FALSE;
   279 
   280     if (bHasNewData) {
   281         for (i = 0; i < 3; ++i) {
   282             values[i] = fLastAccelerometer[i];
   283         }
   284         bHasNewData = false;
   285         retval = SDL_TRUE;
   286     }
   287 
   288     return retval;
   289 }
   290 
   291 //
   292 // Audio support
   293 //
   294 static jboolean audioBuffer16Bit = JNI_FALSE;
   295 static jboolean audioBufferStereo = JNI_FALSE;
   296 static jobject audioBuffer = NULL;
   297 static void* audioBufferPinned = NULL;
   298 
   299 extern "C" int Android_JNI_OpenAudioDevice(int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
   300 {
   301     int audioBufferFrames;
   302 
   303     int status;
   304     JNIEnv *env;
   305     static bool isAttached = false;    
   306     status = mJavaVM->GetEnv((void **) &env, JNI_VERSION_1_4);
   307     if(status < 0) {
   308         LOGE("callback_handler: failed to get JNI environment, assuming native thread");
   309         status = mJavaVM->AttachCurrentThread(&env, NULL);
   310         if(status < 0) {
   311             LOGE("callback_handler: failed to attach current thread");
   312             return 0;
   313         }
   314         isAttached = true;
   315     }
   316 
   317     
   318     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device");
   319     audioBuffer16Bit = is16Bit;
   320     audioBufferStereo = channelCount > 1;
   321 
   322     audioBuffer = env->CallStaticObjectMethod(mActivityClass, midAudioInit, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames);
   323 
   324     if (audioBuffer == NULL) {
   325         __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: didn't get back a good audio buffer!");
   326         return 0;
   327     }
   328     audioBuffer = env->NewGlobalRef(audioBuffer);
   329 
   330     jboolean isCopy = JNI_FALSE;
   331     if (audioBuffer16Bit) {
   332         audioBufferPinned = env->GetShortArrayElements((jshortArray)audioBuffer, &isCopy);
   333         audioBufferFrames = env->GetArrayLength((jshortArray)audioBuffer);
   334     } else {
   335         audioBufferPinned = env->GetByteArrayElements((jbyteArray)audioBuffer, &isCopy);
   336         audioBufferFrames = env->GetArrayLength((jbyteArray)audioBuffer);
   337     }
   338     if (audioBufferStereo) {
   339         audioBufferFrames /= 2;
   340     }
   341  
   342     if (isAttached) {
   343         mJavaVM->DetachCurrentThread();
   344     }
   345 
   346     return audioBufferFrames;
   347 }
   348 
   349 extern "C" void * Android_JNI_GetAudioBuffer()
   350 {
   351     return audioBufferPinned;
   352 }
   353 
   354 extern "C" void Android_JNI_WriteAudioBuffer()
   355 {
   356     if (audioBuffer16Bit) {
   357         mAudioEnv->ReleaseShortArrayElements((jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
   358         mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
   359     } else {
   360         mAudioEnv->ReleaseByteArrayElements((jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT);
   361         mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer);
   362     }
   363 
   364     /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
   365 }
   366 
   367 extern "C" void Android_JNI_CloseAudioDevice()
   368 {
   369     int status;
   370     JNIEnv *env;
   371     static bool isAttached = false;    
   372     status = mJavaVM->GetEnv((void **) &env, JNI_VERSION_1_4);
   373     if(status < 0) {
   374         LOGE("callback_handler: failed to get JNI environment, assuming native thread");
   375         status = mJavaVM->AttachCurrentThread(&env, NULL);
   376         if(status < 0) {
   377             LOGE("callback_handler: failed to attach current thread");
   378             return;
   379         }
   380         isAttached = true;
   381     }
   382 
   383     env->CallStaticVoidMethod(mActivityClass, midAudioQuit); 
   384 
   385     if (audioBuffer) {
   386         env->DeleteGlobalRef(audioBuffer);
   387         audioBuffer = NULL;
   388         audioBufferPinned = NULL;
   389     }
   390 
   391     if (isAttached) {
   392         mJavaVM->DetachCurrentThread();
   393     }
   394 }
   395 
   396 // Test for an exception and call SDL_SetError with its detail if one occurs
   397 static bool Android_JNI_ExceptionOccurred()
   398 {
   399     SDL_assert(LocalReferenceHolder::IsActive());
   400 
   401     jthrowable exception = mEnv->ExceptionOccurred();
   402     if (exception != NULL) {
   403         jmethodID mid;
   404 
   405         // Until this happens most JNI operations have undefined behaviour
   406         mEnv->ExceptionClear();
   407 
   408         jclass exceptionClass = mEnv->GetObjectClass(exception);
   409         jclass classClass = mEnv->FindClass("java/lang/Class");
   410 
   411         mid = mEnv->GetMethodID(classClass, "getName", "()Ljava/lang/String;");
   412         jstring exceptionName = (jstring)mEnv->CallObjectMethod(exceptionClass, mid);
   413         const char* exceptionNameUTF8 = mEnv->GetStringUTFChars(exceptionName, 0);
   414 
   415         mid = mEnv->GetMethodID(exceptionClass, "getMessage", "()Ljava/lang/String;");
   416         jstring exceptionMessage = (jstring)mEnv->CallObjectMethod(exception, mid);
   417 
   418         if (exceptionMessage != NULL) {
   419             const char* exceptionMessageUTF8 = mEnv->GetStringUTFChars(
   420                     exceptionMessage, 0);
   421             SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
   422             mEnv->ReleaseStringUTFChars(exceptionMessage, exceptionMessageUTF8);
   423         } else {
   424             SDL_SetError("%s", exceptionNameUTF8);
   425         }
   426 
   427         mEnv->ReleaseStringUTFChars(exceptionName, exceptionNameUTF8);
   428 
   429         return true;
   430     }
   431 
   432     return false;
   433 }
   434 
   435 static int Android_JNI_FileOpen(SDL_RWops* ctx)
   436 {
   437     LocalReferenceHolder refs;
   438     int result = 0;
   439 
   440     jmethodID mid;
   441     jobject context;
   442     jobject assetManager;
   443     jobject inputStream;
   444     jclass channels;
   445     jobject readableByteChannel;
   446     jstring fileNameJString;
   447 
   448     if (!refs.init(mEnv)) {
   449         goto failure;
   450     }
   451 
   452     fileNameJString = (jstring)ctx->hidden.androidio.fileNameRef;
   453 
   454     // context = SDLActivity.getContext();
   455     mid = mEnv->GetStaticMethodID(mActivityClass,
   456             "getContext","()Landroid/content/Context;");
   457     context = mEnv->CallStaticObjectMethod(mActivityClass, mid);
   458 
   459     // assetManager = context.getAssets();
   460     mid = mEnv->GetMethodID(mEnv->GetObjectClass(context),
   461             "getAssets", "()Landroid/content/res/AssetManager;");
   462     assetManager = mEnv->CallObjectMethod(context, mid);
   463 
   464     // inputStream = assetManager.open(<filename>);
   465     mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager),
   466             "open", "(Ljava/lang/String;)Ljava/io/InputStream;");
   467     inputStream = mEnv->CallObjectMethod(assetManager, mid, fileNameJString);
   468     if (Android_JNI_ExceptionOccurred()) {
   469         goto failure;
   470     }
   471 
   472     ctx->hidden.androidio.inputStreamRef = mEnv->NewGlobalRef(inputStream);
   473 
   474     // Despite all the visible documentation on [Asset]InputStream claiming
   475     // that the .available() method is not guaranteed to return the entire file
   476     // size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
   477     // android/apis/content/ReadAsset.java imply that Android's
   478     // AssetInputStream.available() /will/ always return the total file size
   479 
   480     // size = inputStream.available();
   481     mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   482             "available", "()I");
   483     ctx->hidden.androidio.size = mEnv->CallIntMethod(inputStream, mid);
   484     if (Android_JNI_ExceptionOccurred()) {
   485         goto failure;
   486     }
   487 
   488     // readableByteChannel = Channels.newChannel(inputStream);
   489     channels = mEnv->FindClass("java/nio/channels/Channels");
   490     mid = mEnv->GetStaticMethodID(channels,
   491             "newChannel",
   492             "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
   493     readableByteChannel = mEnv->CallStaticObjectMethod(
   494             channels, mid, inputStream);
   495     if (Android_JNI_ExceptionOccurred()) {
   496         goto failure;
   497     }
   498 
   499     ctx->hidden.androidio.readableByteChannelRef =
   500         mEnv->NewGlobalRef(readableByteChannel);
   501 
   502     // Store .read id for reading purposes
   503     mid = mEnv->GetMethodID(mEnv->GetObjectClass(readableByteChannel),
   504             "read", "(Ljava/nio/ByteBuffer;)I");
   505     ctx->hidden.androidio.readMethod = mid;
   506 
   507     ctx->hidden.androidio.position = 0;
   508 
   509     if (false) {
   510 failure:
   511         result = -1;
   512 
   513         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   514 
   515         if(ctx->hidden.androidio.inputStreamRef != NULL) {
   516             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   517         }
   518 
   519         if(ctx->hidden.androidio.readableByteChannelRef != NULL) {
   520             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
   521         }
   522 
   523     }
   524 
   525     return result;
   526 }
   527 
   528 extern "C" int Android_JNI_FileOpen(SDL_RWops* ctx,
   529         const char* fileName, const char*)
   530 {
   531     LocalReferenceHolder refs;
   532 
   533     if (!refs.init(mEnv)) {
   534         return -1;
   535     }
   536 
   537     if (!ctx) {
   538         return -1;
   539     }
   540 
   541     jstring fileNameJString = mEnv->NewStringUTF(fileName);
   542     ctx->hidden.androidio.fileNameRef = mEnv->NewGlobalRef(fileNameJString);
   543     ctx->hidden.androidio.inputStreamRef = NULL;
   544 
   545     return Android_JNI_FileOpen(ctx);
   546 }
   547 
   548 extern "C" size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
   549         size_t size, size_t maxnum)
   550 {
   551     LocalReferenceHolder refs;
   552     int bytesRemaining = size * maxnum;
   553     int bytesRead = 0;
   554 
   555     if (!refs.init(mEnv)) {
   556         return -1;
   557     }
   558 
   559     jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannelRef;
   560     jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
   561     jobject byteBuffer = mEnv->NewDirectByteBuffer(buffer, bytesRemaining);
   562 
   563     while (bytesRemaining > 0) {
   564         // result = readableByteChannel.read(...);
   565         int result = mEnv->CallIntMethod(readableByteChannel, readMethod, byteBuffer);
   566 
   567         if (Android_JNI_ExceptionOccurred()) {
   568             return 0;
   569         }
   570 
   571         if (result < 0) {
   572             break;
   573         }
   574 
   575         bytesRemaining -= result;
   576         bytesRead += result;
   577         ctx->hidden.androidio.position += result;
   578     }
   579 
   580     return bytesRead / size;
   581 }
   582 
   583 extern "C" size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
   584         size_t size, size_t num)
   585 {
   586     SDL_SetError("Cannot write to Android package filesystem");
   587     return 0;
   588 }
   589 
   590 static int Android_JNI_FileClose(SDL_RWops* ctx, bool release)
   591 {
   592     LocalReferenceHolder refs;
   593     int result = 0;
   594 
   595     if (!refs.init(mEnv)) {
   596         SDL_SetError("Failed to allocate enough JVM local references");
   597         return -1;
   598     }
   599 
   600     if (ctx) {
   601         if (release) {
   602             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   603         }
   604 
   605         jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
   606 
   607         // inputStream.close();
   608         jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   609                 "close", "()V");
   610         mEnv->CallVoidMethod(inputStream, mid);
   611         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   612         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
   613         if (Android_JNI_ExceptionOccurred()) {
   614             result = -1;
   615         }
   616 
   617         if (release) {
   618             SDL_FreeRW(ctx);
   619         }
   620     }
   621 
   622     return result;
   623 }
   624 
   625 
   626 extern "C" long Android_JNI_FileSeek(SDL_RWops* ctx, long offset, int whence)
   627 {
   628     long newPosition;
   629 
   630     switch (whence) {
   631         case RW_SEEK_SET:
   632             newPosition = offset;
   633             break;
   634         case RW_SEEK_CUR:
   635             newPosition = ctx->hidden.androidio.position + offset;
   636             break;
   637         case RW_SEEK_END:
   638             newPosition = ctx->hidden.androidio.size + offset;
   639             break;
   640         default:
   641             SDL_SetError("Unknown value for 'whence'");
   642             return -1;
   643     }
   644     if (newPosition < 0) {
   645         newPosition = 0;
   646     }
   647     if (newPosition > ctx->hidden.androidio.size) {
   648         newPosition = ctx->hidden.androidio.size;
   649     }
   650 
   651     long movement = newPosition - ctx->hidden.androidio.position;
   652     jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
   653 
   654     if (movement > 0) {
   655         unsigned char buffer[1024];
   656 
   657         // The easy case where we're seeking forwards
   658         while (movement > 0) {
   659             long amount = (long) sizeof (buffer);
   660             if (amount > movement) {
   661                 amount = movement;
   662             }
   663             size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount);
   664 
   665             if (result <= 0) {
   666                 // Failed to read/skip the required amount, so fail
   667                 return -1;
   668             }
   669 
   670             movement -= result;
   671         }
   672     } else if (movement < 0) {
   673         // We can't seek backwards so we have to reopen the file and seek
   674         // forwards which obviously isn't very efficient
   675         Android_JNI_FileClose(ctx, false);
   676         Android_JNI_FileOpen(ctx);
   677         Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
   678     }
   679 
   680     ctx->hidden.androidio.position = newPosition;
   681 
   682     return ctx->hidden.androidio.position;
   683 }
   684 
   685 extern "C" int Android_JNI_FileClose(SDL_RWops* ctx)
   686 {
   687     return Android_JNI_FileClose(ctx, true);
   688 }
   689 
   690 #endif /* __ANDROID__ */
   691 
   692 /* vi: set ts=4 sw=4 expandtab: */