src/core/android/SDL_android.cpp
author Gabriel Jacobo <gabomdq@gmail.com>
Fri, 25 May 2012 15:35:41 -0300
changeset 6308 263e2c049c22
parent 6307 6048116f40b1
child 6330 0fa55ca2efdd
permissions -rwxr-xr-x
Fixes issue #1500 "SDL_RWops fails under Android 4" by removing stale Local Refs
and replacing them for their global equivalents.
     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         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_LOST, 0, 0);
   180         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_MINIMIZED, 0, 0);
   181     }
   182 }
   183 
   184 // Resume
   185 extern "C" void Java_org_libsdl_app_SDLActivity_nativeResume(
   186                                     JNIEnv* env, jclass cls)
   187 {
   188     if (Android_Window) {
   189         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_GAINED, 0, 0);
   190         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_RESTORED, 0, 0);
   191     }
   192 }
   193 
   194 extern "C" void Java_org_libsdl_app_SDLActivity_nativeRunAudioThread(
   195                                     JNIEnv* env, jclass cls)
   196 {
   197     /* This is the audio thread, with a different environment */
   198     mAudioEnv = env;
   199 
   200     Android_RunAudioThread();
   201 }
   202 
   203 
   204 /*******************************************************************************
   205              Functions called by SDL into Java
   206 *******************************************************************************/
   207 
   208 class LocalReferenceHolder
   209 {
   210 private:
   211     static int s_active;
   212 
   213 public:
   214     static bool IsActive() {
   215         return s_active > 0;
   216     }
   217 
   218 public:
   219     LocalReferenceHolder() : m_env(NULL) { }
   220     ~LocalReferenceHolder() {
   221         if (m_env) {
   222             m_env->PopLocalFrame(NULL);
   223             --s_active;
   224         }
   225     }
   226 
   227     bool init(JNIEnv *env, jint capacity = 16) {
   228         if (env->PushLocalFrame(capacity) < 0) {
   229             SDL_SetError("Failed to allocate enough JVM local references");
   230             return false;
   231         }
   232         ++s_active;
   233         m_env = env;
   234         return true;
   235     }
   236 
   237 protected:
   238     JNIEnv *m_env;
   239 };
   240 int LocalReferenceHolder::s_active;
   241 
   242 extern "C" SDL_bool Android_JNI_CreateContext(int majorVersion, int minorVersion)
   243 {
   244     if (mEnv->CallStaticBooleanMethod(mActivityClass, midCreateGLContext, majorVersion, minorVersion)) {
   245         return SDL_TRUE;
   246     } else {
   247         return SDL_FALSE;
   248     }
   249 }
   250 
   251 extern "C" void Android_JNI_SwapWindow()
   252 {
   253     mEnv->CallStaticVoidMethod(mActivityClass, midFlipBuffers); 
   254 }
   255 
   256 extern "C" void Android_JNI_SetActivityTitle(const char *title)
   257 {
   258     jmethodID mid;
   259 
   260     mid = mEnv->GetStaticMethodID(mActivityClass,"setActivityTitle","(Ljava/lang/String;)V");
   261     if (mid) {
   262         jstring jtitle = reinterpret_cast<jstring>(mEnv->NewStringUTF(title));
   263         mEnv->CallStaticVoidMethod(mActivityClass, mid, jtitle);
   264         mEnv->DeleteLocalRef(jtitle);
   265     }
   266 }
   267 
   268 extern "C" SDL_bool Android_JNI_GetAccelerometerValues(float values[3])
   269 {
   270     int i;
   271     SDL_bool retval = SDL_FALSE;
   272 
   273     if (bHasNewData) {
   274         for (i = 0; i < 3; ++i) {
   275             values[i] = fLastAccelerometer[i];
   276         }
   277         bHasNewData = false;
   278         retval = SDL_TRUE;
   279     }
   280 
   281     return retval;
   282 }
   283 
   284 //
   285 // Audio support
   286 //
   287 static jboolean audioBuffer16Bit = JNI_FALSE;
   288 static jboolean audioBufferStereo = JNI_FALSE;
   289 static jobject audioBuffer = NULL;
   290 static void* audioBufferPinned = NULL;
   291 
   292 extern "C" int Android_JNI_OpenAudioDevice(int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
   293 {
   294     int audioBufferFrames;
   295 
   296     int status;
   297     JNIEnv *env;
   298     static bool isAttached = false;    
   299     status = mJavaVM->GetEnv((void **) &env, JNI_VERSION_1_4);
   300     if(status < 0) {
   301         LOGE("callback_handler: failed to get JNI environment, assuming native thread");
   302         status = mJavaVM->AttachCurrentThread(&env, NULL);
   303         if(status < 0) {
   304             LOGE("callback_handler: failed to attach current thread");
   305             return 0;
   306         }
   307         isAttached = true;
   308     }
   309 
   310     
   311     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device");
   312     audioBuffer16Bit = is16Bit;
   313     audioBufferStereo = channelCount > 1;
   314 
   315     audioBuffer = env->CallStaticObjectMethod(mActivityClass, midAudioInit, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames);
   316 
   317     if (audioBuffer == NULL) {
   318         __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: didn't get back a good audio buffer!");
   319         return 0;
   320     }
   321     audioBuffer = env->NewGlobalRef(audioBuffer);
   322 
   323     jboolean isCopy = JNI_FALSE;
   324     if (audioBuffer16Bit) {
   325         audioBufferPinned = env->GetShortArrayElements((jshortArray)audioBuffer, &isCopy);
   326         audioBufferFrames = env->GetArrayLength((jshortArray)audioBuffer);
   327     } else {
   328         audioBufferPinned = env->GetByteArrayElements((jbyteArray)audioBuffer, &isCopy);
   329         audioBufferFrames = env->GetArrayLength((jbyteArray)audioBuffer);
   330     }
   331     if (audioBufferStereo) {
   332         audioBufferFrames /= 2;
   333     }
   334  
   335     if (isAttached) {
   336         mJavaVM->DetachCurrentThread();
   337     }
   338 
   339     return audioBufferFrames;
   340 }
   341 
   342 extern "C" void * Android_JNI_GetAudioBuffer()
   343 {
   344     return audioBufferPinned;
   345 }
   346 
   347 extern "C" void Android_JNI_WriteAudioBuffer()
   348 {
   349     if (audioBuffer16Bit) {
   350         mAudioEnv->ReleaseShortArrayElements((jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
   351         mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
   352     } else {
   353         mAudioEnv->ReleaseByteArrayElements((jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT);
   354         mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer);
   355     }
   356 
   357     /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
   358 }
   359 
   360 extern "C" void Android_JNI_CloseAudioDevice()
   361 {
   362     int status;
   363     JNIEnv *env;
   364     static bool isAttached = false;    
   365     status = mJavaVM->GetEnv((void **) &env, JNI_VERSION_1_4);
   366     if(status < 0) {
   367         LOGE("callback_handler: failed to get JNI environment, assuming native thread");
   368         status = mJavaVM->AttachCurrentThread(&env, NULL);
   369         if(status < 0) {
   370             LOGE("callback_handler: failed to attach current thread");
   371             return;
   372         }
   373         isAttached = true;
   374     }
   375 
   376     env->CallStaticVoidMethod(mActivityClass, midAudioQuit); 
   377 
   378     if (audioBuffer) {
   379         env->DeleteGlobalRef(audioBuffer);
   380         audioBuffer = NULL;
   381         audioBufferPinned = NULL;
   382     }
   383 
   384     if (isAttached) {
   385         mJavaVM->DetachCurrentThread();
   386     }
   387 }
   388 
   389 // Test for an exception and call SDL_SetError with its detail if one occurs
   390 static bool Android_JNI_ExceptionOccurred()
   391 {
   392     SDL_assert(LocalReferenceHolder::IsActive());
   393 
   394     jthrowable exception = mEnv->ExceptionOccurred();
   395     if (exception != NULL) {
   396         jmethodID mid;
   397 
   398         // Until this happens most JNI operations have undefined behaviour
   399         mEnv->ExceptionClear();
   400 
   401         jclass exceptionClass = mEnv->GetObjectClass(exception);
   402         jclass classClass = mEnv->FindClass("java/lang/Class");
   403 
   404         mid = mEnv->GetMethodID(classClass, "getName", "()Ljava/lang/String;");
   405         jstring exceptionName = (jstring)mEnv->CallObjectMethod(exceptionClass, mid);
   406         const char* exceptionNameUTF8 = mEnv->GetStringUTFChars(exceptionName, 0);
   407 
   408         mid = mEnv->GetMethodID(exceptionClass, "getMessage", "()Ljava/lang/String;");
   409         jstring exceptionMessage = (jstring)mEnv->CallObjectMethod(exception, mid);
   410 
   411         if (exceptionMessage != NULL) {
   412             const char* exceptionMessageUTF8 = mEnv->GetStringUTFChars(
   413                     exceptionMessage, 0);
   414             SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
   415             mEnv->ReleaseStringUTFChars(exceptionMessage, exceptionMessageUTF8);
   416         } else {
   417             SDL_SetError("%s", exceptionNameUTF8);
   418         }
   419 
   420         mEnv->ReleaseStringUTFChars(exceptionName, exceptionNameUTF8);
   421 
   422         return true;
   423     }
   424 
   425     return false;
   426 }
   427 
   428 static int Android_JNI_FileOpen(SDL_RWops* ctx)
   429 {
   430     LocalReferenceHolder refs;
   431     int result = 0;
   432 
   433     jmethodID mid;
   434     jobject context;
   435     jobject assetManager;
   436     jobject inputStream;
   437     jclass channels;
   438     jobject readableByteChannel;
   439     jstring fileNameJString;
   440 
   441     if (!refs.init(mEnv)) {
   442         goto failure;
   443     }
   444 
   445     fileNameJString = (jstring)ctx->hidden.androidio.fileNameRef;
   446 
   447     // context = SDLActivity.getContext();
   448     mid = mEnv->GetStaticMethodID(mActivityClass,
   449             "getContext","()Landroid/content/Context;");
   450     context = mEnv->CallStaticObjectMethod(mActivityClass, mid);
   451 
   452     // assetManager = context.getAssets();
   453     mid = mEnv->GetMethodID(mEnv->GetObjectClass(context),
   454             "getAssets", "()Landroid/content/res/AssetManager;");
   455     assetManager = mEnv->CallObjectMethod(context, mid);
   456 
   457     // inputStream = assetManager.open(<filename>);
   458     mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager),
   459             "open", "(Ljava/lang/String;)Ljava/io/InputStream;");
   460     inputStream = mEnv->CallObjectMethod(assetManager, mid, fileNameJString);
   461     if (Android_JNI_ExceptionOccurred()) {
   462         goto failure;
   463     }
   464 
   465     ctx->hidden.androidio.inputStreamRef = mEnv->NewGlobalRef(inputStream);
   466 
   467     // Despite all the visible documentation on [Asset]InputStream claiming
   468     // that the .available() method is not guaranteed to return the entire file
   469     // size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
   470     // android/apis/content/ReadAsset.java imply that Android's
   471     // AssetInputStream.available() /will/ always return the total file size
   472 
   473     // size = inputStream.available();
   474     mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   475             "available", "()I");
   476     ctx->hidden.androidio.size = mEnv->CallIntMethod(inputStream, mid);
   477     if (Android_JNI_ExceptionOccurred()) {
   478         goto failure;
   479     }
   480 
   481     // readableByteChannel = Channels.newChannel(inputStream);
   482     channels = mEnv->FindClass("java/nio/channels/Channels");
   483     mid = mEnv->GetStaticMethodID(channels,
   484             "newChannel",
   485             "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
   486     readableByteChannel = mEnv->CallStaticObjectMethod(
   487             channels, mid, inputStream);
   488     if (Android_JNI_ExceptionOccurred()) {
   489         goto failure;
   490     }
   491 
   492     ctx->hidden.androidio.readableByteChannelRef =
   493         mEnv->NewGlobalRef(readableByteChannel);
   494 
   495     // Store .read id for reading purposes
   496     mid = mEnv->GetMethodID(mEnv->GetObjectClass(readableByteChannel),
   497             "read", "(Ljava/nio/ByteBuffer;)I");
   498     ctx->hidden.androidio.readMethod = mid;
   499 
   500     ctx->hidden.androidio.position = 0;
   501 
   502     if (false) {
   503 failure:
   504         result = -1;
   505 
   506         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   507 
   508         if(ctx->hidden.androidio.inputStreamRef != NULL) {
   509             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   510         }
   511 
   512         if(ctx->hidden.androidio.readableByteChannelRef != NULL) {
   513             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
   514         }
   515 
   516     }
   517 
   518     return result;
   519 }
   520 
   521 extern "C" int Android_JNI_FileOpen(SDL_RWops* ctx,
   522         const char* fileName, const char*)
   523 {
   524     LocalReferenceHolder refs;
   525 
   526     if (!refs.init(mEnv)) {
   527         return -1;
   528     }
   529 
   530     if (!ctx) {
   531         return -1;
   532     }
   533 
   534     jstring fileNameJString = mEnv->NewStringUTF(fileName);
   535     ctx->hidden.androidio.fileNameRef = mEnv->NewGlobalRef(fileNameJString);
   536     ctx->hidden.androidio.inputStreamRef = NULL;
   537 
   538     return Android_JNI_FileOpen(ctx);
   539 }
   540 
   541 extern "C" size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
   542         size_t size, size_t maxnum)
   543 {
   544     LocalReferenceHolder refs;
   545     int bytesRemaining = size * maxnum;
   546     int bytesRead = 0;
   547 
   548     if (!refs.init(mEnv)) {
   549         return -1;
   550     }
   551 
   552     jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannelRef;
   553     jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
   554     jobject byteBuffer = mEnv->NewDirectByteBuffer(buffer, bytesRemaining);
   555 
   556     while (bytesRemaining > 0) {
   557         // result = readableByteChannel.read(...);
   558         int result = mEnv->CallIntMethod(readableByteChannel, readMethod, byteBuffer);
   559 
   560         if (Android_JNI_ExceptionOccurred()) {
   561             return 0;
   562         }
   563 
   564         if (result < 0) {
   565             break;
   566         }
   567 
   568         bytesRemaining -= result;
   569         bytesRead += result;
   570         ctx->hidden.androidio.position += result;
   571     }
   572 
   573     return bytesRead / size;
   574 }
   575 
   576 extern "C" size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
   577         size_t size, size_t num)
   578 {
   579     SDL_SetError("Cannot write to Android package filesystem");
   580     return 0;
   581 }
   582 
   583 static int Android_JNI_FileClose(SDL_RWops* ctx, bool release)
   584 {
   585     LocalReferenceHolder refs;
   586     int result = 0;
   587 
   588     if (!refs.init(mEnv)) {
   589         SDL_SetError("Failed to allocate enough JVM local references");
   590         return -1;
   591     }
   592 
   593     if (ctx) {
   594         if (release) {
   595             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   596         }
   597 
   598         jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
   599 
   600         // inputStream.close();
   601         jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   602                 "close", "()V");
   603         mEnv->CallVoidMethod(inputStream, mid);
   604         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   605         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
   606         if (Android_JNI_ExceptionOccurred()) {
   607             result = -1;
   608         }
   609 
   610         if (release) {
   611             SDL_FreeRW(ctx);
   612         }
   613     }
   614 
   615     return result;
   616 }
   617 
   618 
   619 extern "C" long Android_JNI_FileSeek(SDL_RWops* ctx, long offset, int whence)
   620 {
   621     long newPosition;
   622 
   623     switch (whence) {
   624         case RW_SEEK_SET:
   625             newPosition = offset;
   626             break;
   627         case RW_SEEK_CUR:
   628             newPosition = ctx->hidden.androidio.position + offset;
   629             break;
   630         case RW_SEEK_END:
   631             newPosition = ctx->hidden.androidio.size + offset;
   632             break;
   633         default:
   634             SDL_SetError("Unknown value for 'whence'");
   635             return -1;
   636     }
   637     if (newPosition < 0) {
   638         newPosition = 0;
   639     }
   640     if (newPosition > ctx->hidden.androidio.size) {
   641         newPosition = ctx->hidden.androidio.size;
   642     }
   643 
   644     long movement = newPosition - ctx->hidden.androidio.position;
   645     jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
   646 
   647     if (movement > 0) {
   648         unsigned char buffer[1024];
   649 
   650         // The easy case where we're seeking forwards
   651         while (movement > 0) {
   652             long amount = (long) sizeof (buffer);
   653             if (amount > movement) {
   654                 amount = movement;
   655             }
   656             size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount);
   657 
   658             if (result <= 0) {
   659                 // Failed to read/skip the required amount, so fail
   660                 return -1;
   661             }
   662 
   663             movement -= result;
   664         }
   665     } else if (movement < 0) {
   666         // We can't seek backwards so we have to reopen the file and seek
   667         // forwards which obviously isn't very efficient
   668         Android_JNI_FileClose(ctx, false);
   669         Android_JNI_FileOpen(ctx);
   670         Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
   671     }
   672 
   673     ctx->hidden.androidio.position = newPosition;
   674 
   675     return ctx->hidden.androidio.position;
   676 }
   677 
   678 extern "C" int Android_JNI_FileClose(SDL_RWops* ctx)
   679 {
   680     return Android_JNI_FileClose(ctx, true);
   681 }
   682 
   683 #endif /* __ANDROID__ */
   684 
   685 /* vi: set ts=4 sw=4 expandtab: */