src/core/android/SDL_android.cpp
author Sam Lantinga <slouken@libsdl.org>
Sun, 12 Feb 2012 20:57:32 -0500
changeset 6284 1893d507ba42
parent 6212 78d854de3a66
child 6307 6048116f40b1
permissions -rwxr-xr-x
Fixed bug 1417 - Android_JNI_FileClose local reference bug

A better solution for automatic local reference management.
     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         mEnv->CallStaticVoidMethod(mActivityClass, mid, mEnv->NewStringUTF(title));
   263     }
   264 }
   265 
   266 extern "C" SDL_bool Android_JNI_GetAccelerometerValues(float values[3])
   267 {
   268     int i;
   269     SDL_bool retval = SDL_FALSE;
   270 
   271     if (bHasNewData) {
   272         for (i = 0; i < 3; ++i) {
   273             values[i] = fLastAccelerometer[i];
   274         }
   275         bHasNewData = false;
   276         retval = SDL_TRUE;
   277     }
   278 
   279     return retval;
   280 }
   281 
   282 //
   283 // Audio support
   284 //
   285 static jboolean audioBuffer16Bit = JNI_FALSE;
   286 static jboolean audioBufferStereo = JNI_FALSE;
   287 static jobject audioBuffer = NULL;
   288 static void* audioBufferPinned = NULL;
   289 
   290 extern "C" int Android_JNI_OpenAudioDevice(int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
   291 {
   292     int audioBufferFrames;
   293 
   294     int status;
   295     JNIEnv *env;
   296     static bool isAttached = false;    
   297     status = mJavaVM->GetEnv((void **) &env, JNI_VERSION_1_4);
   298     if(status < 0) {
   299         LOGE("callback_handler: failed to get JNI environment, assuming native thread");
   300         status = mJavaVM->AttachCurrentThread(&env, NULL);
   301         if(status < 0) {
   302             LOGE("callback_handler: failed to attach current thread");
   303             return 0;
   304         }
   305         isAttached = true;
   306     }
   307 
   308     
   309     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device");
   310     audioBuffer16Bit = is16Bit;
   311     audioBufferStereo = channelCount > 1;
   312 
   313     audioBuffer = env->CallStaticObjectMethod(mActivityClass, midAudioInit, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames);
   314 
   315     if (audioBuffer == NULL) {
   316         __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: didn't get back a good audio buffer!");
   317         return 0;
   318     }
   319     audioBuffer = env->NewGlobalRef(audioBuffer);
   320 
   321     jboolean isCopy = JNI_FALSE;
   322     if (audioBuffer16Bit) {
   323         audioBufferPinned = env->GetShortArrayElements((jshortArray)audioBuffer, &isCopy);
   324         audioBufferFrames = env->GetArrayLength((jshortArray)audioBuffer);
   325     } else {
   326         audioBufferPinned = env->GetByteArrayElements((jbyteArray)audioBuffer, &isCopy);
   327         audioBufferFrames = env->GetArrayLength((jbyteArray)audioBuffer);
   328     }
   329     if (audioBufferStereo) {
   330         audioBufferFrames /= 2;
   331     }
   332  
   333     if (isAttached) {
   334         mJavaVM->DetachCurrentThread();
   335     }
   336 
   337     return audioBufferFrames;
   338 }
   339 
   340 extern "C" void * Android_JNI_GetAudioBuffer()
   341 {
   342     return audioBufferPinned;
   343 }
   344 
   345 extern "C" void Android_JNI_WriteAudioBuffer()
   346 {
   347     if (audioBuffer16Bit) {
   348         mAudioEnv->ReleaseShortArrayElements((jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
   349         mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
   350     } else {
   351         mAudioEnv->ReleaseByteArrayElements((jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT);
   352         mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer);
   353     }
   354 
   355     /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
   356 }
   357 
   358 extern "C" void Android_JNI_CloseAudioDevice()
   359 {
   360     int status;
   361     JNIEnv *env;
   362     static bool isAttached = false;    
   363     status = mJavaVM->GetEnv((void **) &env, JNI_VERSION_1_4);
   364     if(status < 0) {
   365         LOGE("callback_handler: failed to get JNI environment, assuming native thread");
   366         status = mJavaVM->AttachCurrentThread(&env, NULL);
   367         if(status < 0) {
   368             LOGE("callback_handler: failed to attach current thread");
   369             return;
   370         }
   371         isAttached = true;
   372     }
   373 
   374     env->CallStaticVoidMethod(mActivityClass, midAudioQuit); 
   375 
   376     if (audioBuffer) {
   377         env->DeleteGlobalRef(audioBuffer);
   378         audioBuffer = NULL;
   379         audioBufferPinned = NULL;
   380     }
   381 
   382     if (isAttached) {
   383         mJavaVM->DetachCurrentThread();
   384     }
   385 }
   386 
   387 // Test for an exception and call SDL_SetError with its detail if one occurs
   388 static bool Android_JNI_ExceptionOccurred()
   389 {
   390     SDL_assert(LocalReferenceHolder::IsActive());
   391 
   392     jthrowable exception = mEnv->ExceptionOccurred();
   393     if (exception != NULL) {
   394         jmethodID mid;
   395 
   396         // Until this happens most JNI operations have undefined behaviour
   397         mEnv->ExceptionClear();
   398 
   399         jclass exceptionClass = mEnv->GetObjectClass(exception);
   400         jclass classClass = mEnv->FindClass("java/lang/Class");
   401 
   402         mid = mEnv->GetMethodID(classClass, "getName", "()Ljava/lang/String;");
   403         jstring exceptionName = (jstring)mEnv->CallObjectMethod(exceptionClass, mid);
   404         const char* exceptionNameUTF8 = mEnv->GetStringUTFChars(exceptionName, 0);
   405 
   406         mid = mEnv->GetMethodID(exceptionClass, "getMessage", "()Ljava/lang/String;");
   407         jstring exceptionMessage = (jstring)mEnv->CallObjectMethod(exception, mid);
   408 
   409         if (exceptionMessage != NULL) {
   410             const char* exceptionMessageUTF8 = mEnv->GetStringUTFChars(
   411                     exceptionMessage, 0);
   412             SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
   413             mEnv->ReleaseStringUTFChars(exceptionMessage, exceptionMessageUTF8);
   414         } else {
   415             SDL_SetError("%s", exceptionNameUTF8);
   416         }
   417 
   418         mEnv->ReleaseStringUTFChars(exceptionName, exceptionNameUTF8);
   419 
   420         return true;
   421     }
   422 
   423     return false;
   424 }
   425 
   426 static int Android_JNI_FileOpen(SDL_RWops* ctx)
   427 {
   428     LocalReferenceHolder refs;
   429     int result = 0;
   430 
   431     jmethodID mid;
   432     jobject context;
   433     jobject assetManager;
   434     jobject inputStream;
   435     jclass channels;
   436     jobject readableByteChannel;
   437     jstring fileNameJString;
   438 
   439     if (!refs.init(mEnv)) {
   440         goto failure;
   441     }
   442 
   443     fileNameJString = (jstring)ctx->hidden.androidio.fileName;
   444 
   445     // context = SDLActivity.getContext();
   446     mid = mEnv->GetStaticMethodID(mActivityClass,
   447             "getContext","()Landroid/content/Context;");
   448     context = mEnv->CallStaticObjectMethod(mActivityClass, mid);
   449 
   450     // assetManager = context.getAssets();
   451     mid = mEnv->GetMethodID(mEnv->GetObjectClass(context),
   452             "getAssets", "()Landroid/content/res/AssetManager;");
   453     assetManager = mEnv->CallObjectMethod(context, mid);
   454 
   455     // inputStream = assetManager.open(<filename>);
   456     mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager),
   457             "open", "(Ljava/lang/String;)Ljava/io/InputStream;");
   458     inputStream = mEnv->CallObjectMethod(assetManager, mid, fileNameJString);
   459     if (Android_JNI_ExceptionOccurred()) {
   460         goto failure;
   461     }
   462 
   463     ctx->hidden.androidio.inputStream = inputStream;
   464     ctx->hidden.androidio.inputStreamRef = mEnv->NewGlobalRef(inputStream);
   465 
   466     // Despite all the visible documentation on [Asset]InputStream claiming
   467     // that the .available() method is not guaranteed to return the entire file
   468     // size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
   469     // android/apis/content/ReadAsset.java imply that Android's
   470     // AssetInputStream.available() /will/ always return the total file size
   471 
   472     // size = inputStream.available();
   473     mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   474             "available", "()I");
   475     ctx->hidden.androidio.size = mEnv->CallIntMethod(inputStream, mid);
   476     if (Android_JNI_ExceptionOccurred()) {
   477         goto failure;
   478     }
   479 
   480     // readableByteChannel = Channels.newChannel(inputStream);
   481     channels = mEnv->FindClass("java/nio/channels/Channels");
   482     mid = mEnv->GetStaticMethodID(channels,
   483             "newChannel",
   484             "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
   485     readableByteChannel = mEnv->CallStaticObjectMethod(
   486             channels, mid, inputStream);
   487     if (Android_JNI_ExceptionOccurred()) {
   488         goto failure;
   489     }
   490 
   491     ctx->hidden.androidio.readableByteChannel = readableByteChannel;
   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 
   513     return result;
   514 }
   515 
   516 extern "C" int Android_JNI_FileOpen(SDL_RWops* ctx,
   517         const char* fileName, const char*)
   518 {
   519     LocalReferenceHolder refs;
   520 
   521     if (!refs.init(mEnv)) {
   522         return -1;
   523     }
   524 
   525     if (!ctx) {
   526         return -1;
   527     }
   528 
   529     jstring fileNameJString = mEnv->NewStringUTF(fileName);
   530     ctx->hidden.androidio.fileName = fileNameJString;
   531     ctx->hidden.androidio.fileNameRef = mEnv->NewGlobalRef(fileNameJString);
   532     ctx->hidden.androidio.inputStreamRef = NULL;
   533 
   534     return Android_JNI_FileOpen(ctx);
   535 }
   536 
   537 extern "C" size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
   538         size_t size, size_t maxnum)
   539 {
   540     LocalReferenceHolder refs;
   541     int bytesRemaining = size * maxnum;
   542     int bytesRead = 0;
   543 
   544     if (!refs.init(mEnv)) {
   545         return -1;
   546     }
   547 
   548     jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannel;
   549     jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
   550     jobject byteBuffer = mEnv->NewDirectByteBuffer(buffer, bytesRemaining);
   551 
   552     while (bytesRemaining > 0) {
   553         // result = readableByteChannel.read(...);
   554         int result = mEnv->CallIntMethod(readableByteChannel, readMethod, byteBuffer);
   555 
   556         if (Android_JNI_ExceptionOccurred()) {
   557             return 0;
   558         }
   559 
   560         if (result < 0) {
   561             break;
   562         }
   563 
   564         bytesRemaining -= result;
   565         bytesRead += result;
   566         ctx->hidden.androidio.position += result;
   567     }
   568 
   569     return bytesRead / size;
   570 }
   571 
   572 extern "C" size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
   573         size_t size, size_t num)
   574 {
   575     SDL_SetError("Cannot write to Android package filesystem");
   576     return 0;
   577 }
   578 
   579 static int Android_JNI_FileClose(SDL_RWops* ctx, bool release)
   580 {
   581     LocalReferenceHolder refs;
   582     int result = 0;
   583 
   584     if (!refs.init(mEnv)) {
   585         SDL_SetError("Failed to allocate enough JVM local references");
   586         return -1;
   587     }
   588 
   589     if (ctx) {
   590         if (release) {
   591             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   592         }
   593 
   594         jobject inputStream = (jobject)ctx->hidden.androidio.inputStream;
   595 
   596         // inputStream.close();
   597         jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   598                 "close", "()V");
   599         mEnv->CallVoidMethod(inputStream, mid);
   600         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   601         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
   602         if (Android_JNI_ExceptionOccurred()) {
   603             result = -1;
   604         }
   605 
   606         if (release) {
   607             SDL_FreeRW(ctx);
   608         }
   609     }
   610 
   611     return result;
   612 }
   613 
   614 
   615 extern "C" long Android_JNI_FileSeek(SDL_RWops* ctx, long offset, int whence)
   616 {
   617     long newPosition;
   618 
   619     switch (whence) {
   620         case RW_SEEK_SET:
   621             newPosition = offset;
   622             break;
   623         case RW_SEEK_CUR:
   624             newPosition = ctx->hidden.androidio.position + offset;
   625             break;
   626         case RW_SEEK_END:
   627             newPosition = ctx->hidden.androidio.size + offset;
   628             break;
   629         default:
   630             SDL_SetError("Unknown value for 'whence'");
   631             return -1;
   632     }
   633     if (newPosition < 0) {
   634         newPosition = 0;
   635     }
   636     if (newPosition > ctx->hidden.androidio.size) {
   637         newPosition = ctx->hidden.androidio.size;
   638     }
   639 
   640     long movement = newPosition - ctx->hidden.androidio.position;
   641     jobject inputStream = (jobject)ctx->hidden.androidio.inputStream;
   642 
   643     if (movement > 0) {
   644         unsigned char buffer[1024];
   645 
   646         // The easy case where we're seeking forwards
   647         while (movement > 0) {
   648             long amount = (long) sizeof (buffer);
   649             if (amount > movement) {
   650                 amount = movement;
   651             }
   652             size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount);
   653 
   654             if (result <= 0) {
   655                 // Failed to read/skip the required amount, so fail
   656                 return -1;
   657             }
   658 
   659             movement -= result;
   660         }
   661     } else if (movement < 0) {
   662         // We can't seek backwards so we have to reopen the file and seek
   663         // forwards which obviously isn't very efficient
   664         Android_JNI_FileClose(ctx, false);
   665         Android_JNI_FileOpen(ctx);
   666         Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
   667     }
   668 
   669     ctx->hidden.androidio.position = newPosition;
   670 
   671     return ctx->hidden.androidio.position;
   672 }
   673 
   674 extern "C" int Android_JNI_FileClose(SDL_RWops* ctx)
   675 {
   676     return Android_JNI_FileClose(ctx, true);
   677 }
   678 
   679 #endif /* __ANDROID__ */
   680 
   681 /* vi: set ts=4 sw=4 expandtab: */