src/core/android/SDL_android.cpp
author Gabriel Jacobo <gabomdq@gmail.com>
Fri, 25 May 2012 15:10:13 -0300
changeset 6307 6048116f40b1
parent 6284 1893d507ba42
child 6308 263e2c049c22
permissions -rwxr-xr-x
Fix for issue #1465, Leak in Android_JNI_SetActivityTitle (SDL_SetWindowTitle)
Thanks Martin Gerhardy!
     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.fileName;
   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.inputStream = inputStream;
   466     ctx->hidden.androidio.inputStreamRef = mEnv->NewGlobalRef(inputStream);
   467 
   468     // Despite all the visible documentation on [Asset]InputStream claiming
   469     // that the .available() method is not guaranteed to return the entire file
   470     // size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
   471     // android/apis/content/ReadAsset.java imply that Android's
   472     // AssetInputStream.available() /will/ always return the total file size
   473 
   474     // size = inputStream.available();
   475     mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   476             "available", "()I");
   477     ctx->hidden.androidio.size = mEnv->CallIntMethod(inputStream, mid);
   478     if (Android_JNI_ExceptionOccurred()) {
   479         goto failure;
   480     }
   481 
   482     // readableByteChannel = Channels.newChannel(inputStream);
   483     channels = mEnv->FindClass("java/nio/channels/Channels");
   484     mid = mEnv->GetStaticMethodID(channels,
   485             "newChannel",
   486             "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
   487     readableByteChannel = mEnv->CallStaticObjectMethod(
   488             channels, mid, inputStream);
   489     if (Android_JNI_ExceptionOccurred()) {
   490         goto failure;
   491     }
   492 
   493     ctx->hidden.androidio.readableByteChannel = readableByteChannel;
   494     ctx->hidden.androidio.readableByteChannelRef =
   495         mEnv->NewGlobalRef(readableByteChannel);
   496 
   497     // Store .read id for reading purposes
   498     mid = mEnv->GetMethodID(mEnv->GetObjectClass(readableByteChannel),
   499             "read", "(Ljava/nio/ByteBuffer;)I");
   500     ctx->hidden.androidio.readMethod = mid;
   501 
   502     ctx->hidden.androidio.position = 0;
   503 
   504     if (false) {
   505 failure:
   506         result = -1;
   507 
   508         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   509 
   510         if(ctx->hidden.androidio.inputStreamRef != NULL) {
   511             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   512         }
   513     }
   514 
   515     return result;
   516 }
   517 
   518 extern "C" int Android_JNI_FileOpen(SDL_RWops* ctx,
   519         const char* fileName, const char*)
   520 {
   521     LocalReferenceHolder refs;
   522 
   523     if (!refs.init(mEnv)) {
   524         return -1;
   525     }
   526 
   527     if (!ctx) {
   528         return -1;
   529     }
   530 
   531     jstring fileNameJString = mEnv->NewStringUTF(fileName);
   532     ctx->hidden.androidio.fileName = fileNameJString;
   533     ctx->hidden.androidio.fileNameRef = mEnv->NewGlobalRef(fileNameJString);
   534     ctx->hidden.androidio.inputStreamRef = NULL;
   535 
   536     return Android_JNI_FileOpen(ctx);
   537 }
   538 
   539 extern "C" size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
   540         size_t size, size_t maxnum)
   541 {
   542     LocalReferenceHolder refs;
   543     int bytesRemaining = size * maxnum;
   544     int bytesRead = 0;
   545 
   546     if (!refs.init(mEnv)) {
   547         return -1;
   548     }
   549 
   550     jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannel;
   551     jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
   552     jobject byteBuffer = mEnv->NewDirectByteBuffer(buffer, bytesRemaining);
   553 
   554     while (bytesRemaining > 0) {
   555         // result = readableByteChannel.read(...);
   556         int result = mEnv->CallIntMethod(readableByteChannel, readMethod, byteBuffer);
   557 
   558         if (Android_JNI_ExceptionOccurred()) {
   559             return 0;
   560         }
   561 
   562         if (result < 0) {
   563             break;
   564         }
   565 
   566         bytesRemaining -= result;
   567         bytesRead += result;
   568         ctx->hidden.androidio.position += result;
   569     }
   570 
   571     return bytesRead / size;
   572 }
   573 
   574 extern "C" size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
   575         size_t size, size_t num)
   576 {
   577     SDL_SetError("Cannot write to Android package filesystem");
   578     return 0;
   579 }
   580 
   581 static int Android_JNI_FileClose(SDL_RWops* ctx, bool release)
   582 {
   583     LocalReferenceHolder refs;
   584     int result = 0;
   585 
   586     if (!refs.init(mEnv)) {
   587         SDL_SetError("Failed to allocate enough JVM local references");
   588         return -1;
   589     }
   590 
   591     if (ctx) {
   592         if (release) {
   593             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   594         }
   595 
   596         jobject inputStream = (jobject)ctx->hidden.androidio.inputStream;
   597 
   598         // inputStream.close();
   599         jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   600                 "close", "()V");
   601         mEnv->CallVoidMethod(inputStream, mid);
   602         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   603         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
   604         if (Android_JNI_ExceptionOccurred()) {
   605             result = -1;
   606         }
   607 
   608         if (release) {
   609             SDL_FreeRW(ctx);
   610         }
   611     }
   612 
   613     return result;
   614 }
   615 
   616 
   617 extern "C" long Android_JNI_FileSeek(SDL_RWops* ctx, long offset, int whence)
   618 {
   619     long newPosition;
   620 
   621     switch (whence) {
   622         case RW_SEEK_SET:
   623             newPosition = offset;
   624             break;
   625         case RW_SEEK_CUR:
   626             newPosition = ctx->hidden.androidio.position + offset;
   627             break;
   628         case RW_SEEK_END:
   629             newPosition = ctx->hidden.androidio.size + offset;
   630             break;
   631         default:
   632             SDL_SetError("Unknown value for 'whence'");
   633             return -1;
   634     }
   635     if (newPosition < 0) {
   636         newPosition = 0;
   637     }
   638     if (newPosition > ctx->hidden.androidio.size) {
   639         newPosition = ctx->hidden.androidio.size;
   640     }
   641 
   642     long movement = newPosition - ctx->hidden.androidio.position;
   643     jobject inputStream = (jobject)ctx->hidden.androidio.inputStream;
   644 
   645     if (movement > 0) {
   646         unsigned char buffer[1024];
   647 
   648         // The easy case where we're seeking forwards
   649         while (movement > 0) {
   650             long amount = (long) sizeof (buffer);
   651             if (amount > movement) {
   652                 amount = movement;
   653             }
   654             size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount);
   655 
   656             if (result <= 0) {
   657                 // Failed to read/skip the required amount, so fail
   658                 return -1;
   659             }
   660 
   661             movement -= result;
   662         }
   663     } else if (movement < 0) {
   664         // We can't seek backwards so we have to reopen the file and seek
   665         // forwards which obviously isn't very efficient
   666         Android_JNI_FileClose(ctx, false);
   667         Android_JNI_FileOpen(ctx);
   668         Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
   669     }
   670 
   671     ctx->hidden.androidio.position = newPosition;
   672 
   673     return ctx->hidden.androidio.position;
   674 }
   675 
   676 extern "C" int Android_JNI_FileClose(SDL_RWops* ctx)
   677 {
   678     return Android_JNI_FileClose(ctx, true);
   679 }
   680 
   681 #endif /* __ANDROID__ */
   682 
   683 /* vi: set ts=4 sw=4 expandtab: */