src/core/android/SDL_android.cpp
author Sam Lantinga <slouken@libsdl.org>
Fri, 13 Jan 2012 20:57:35 -0500
changeset 6212 78d854de3a66
parent 6191 2c0d35b1af4e
child 6284 1893d507ba42
permissions -rwxr-xr-x
Fixed bug 1368 - Enabling joystick subsystem cause an infinite loop

morgan.devel@gmail.com 2012-01-13 00:32:23 PST

The android version of SDL_SYS_JoystickUpdate doesn't check if there is
actually new data and always generate the SDL_JOYAXISMOTION event.
Consequently, doing a while(SDL_PollEvent()) will result in an endless loop.

The attached patch fix this issue.

It also scale the incoming values properly in the Sint16 range. The scale from
[-gravity;+gravity] is done directly in the java part because one may want to
map the sensor values with a non-linear method for example.
     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 
    24 #ifdef __ANDROID__
    25 
    26 #include "SDL_android.h"
    27 
    28 extern "C" {
    29 #include "../../events/SDL_events_c.h"
    30 #include "../../video/android/SDL_androidkeyboard.h"
    31 #include "../../video/android/SDL_androidtouch.h"
    32 #include "../../video/android/SDL_androidvideo.h"
    33 
    34 #include <android/log.h>
    35 #define LOG_TAG "SDL_android"
    36 //#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
    37 //#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
    38 #define LOGI(...) do {} while (false)
    39 #define LOGE(...) do {} while (false)
    40 
    41 
    42 /* Implemented in audio/android/SDL_androidaudio.c */
    43 extern void Android_RunAudioThread();
    44 } // C
    45 
    46 /*******************************************************************************
    47  This file links the Java side of Android with libsdl
    48 *******************************************************************************/
    49 #include <jni.h>
    50 #include <android/log.h>
    51 
    52 
    53 /*******************************************************************************
    54                                Globals
    55 *******************************************************************************/
    56 static JNIEnv* mEnv = NULL;
    57 static JNIEnv* mAudioEnv = NULL;
    58 static JavaVM* mJavaVM;
    59 
    60 // Main activity
    61 static jclass mActivityClass;
    62 
    63 // method signatures
    64 static jmethodID midCreateGLContext;
    65 static jmethodID midFlipBuffers;
    66 static jmethodID midAudioInit;
    67 static jmethodID midAudioWriteShortBuffer;
    68 static jmethodID midAudioWriteByteBuffer;
    69 static jmethodID midAudioQuit;
    70 
    71 // Accelerometer data storage
    72 static float fLastAccelerometer[3];
    73 static bool bHasNewData;
    74 
    75 /*******************************************************************************
    76                  Functions called by JNI
    77 *******************************************************************************/
    78 
    79 // Library init
    80 extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
    81 {
    82     JNIEnv *env;
    83     mJavaVM = vm;
    84     LOGI("JNI_OnLoad called");
    85     if (mJavaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
    86         LOGE("Failed to get the environment using GetEnv()");
    87         return -1;
    88     }
    89 
    90     return JNI_VERSION_1_4;
    91 }
    92 
    93 // Called before SDL_main() to initialize JNI bindings
    94 extern "C" void SDL_Android_Init(JNIEnv* env, jclass cls)
    95 {
    96     __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init()");
    97 
    98     mEnv = env;
    99     mActivityClass = (jclass)env->NewGlobalRef(cls);
   100 
   101     midCreateGLContext = mEnv->GetStaticMethodID(mActivityClass,
   102                                 "createGLContext","(II)Z");
   103     midFlipBuffers = mEnv->GetStaticMethodID(mActivityClass,
   104                                 "flipBuffers","()V");
   105     midAudioInit = mEnv->GetStaticMethodID(mActivityClass, 
   106                                 "audioInit", "(IZZI)Ljava/lang/Object;");
   107     midAudioWriteShortBuffer = mEnv->GetStaticMethodID(mActivityClass,
   108                                 "audioWriteShortBuffer", "([S)V");
   109     midAudioWriteByteBuffer = mEnv->GetStaticMethodID(mActivityClass,
   110                                 "audioWriteByteBuffer", "([B)V");
   111     midAudioQuit = mEnv->GetStaticMethodID(mActivityClass,
   112                                 "audioQuit", "()V");
   113 
   114     bHasNewData = false;
   115 
   116     if(!midCreateGLContext || !midFlipBuffers || !midAudioInit ||
   117        !midAudioWriteShortBuffer || !midAudioWriteByteBuffer || !midAudioQuit) {
   118         __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL: Couldn't locate Java callbacks, check that they're named and typed correctly");
   119     }
   120     __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init() finished!");
   121 }
   122 
   123 // Resize
   124 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeResize(
   125                                     JNIEnv* env, jclass jcls,
   126                                     jint width, jint height, jint format)
   127 {
   128     Android_SetScreenResolution(width, height, format);
   129 }
   130 
   131 // Keydown
   132 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyDown(
   133                                     JNIEnv* env, jclass jcls, jint keycode)
   134 {
   135     Android_OnKeyDown(keycode);
   136 }
   137 
   138 // Keyup
   139 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyUp(
   140                                     JNIEnv* env, jclass jcls, jint keycode)
   141 {
   142     Android_OnKeyUp(keycode);
   143 }
   144 
   145 // Touch
   146 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeTouch(
   147                                     JNIEnv* env, jclass jcls,
   148                                     jint touch_device_id_in, jint pointer_finger_id_in,
   149                                     jint action, jfloat x, jfloat y, jfloat p)
   150 {
   151     Android_OnTouch(touch_device_id_in, pointer_finger_id_in, action, x, y, p);
   152 }
   153 
   154 // Accelerometer
   155 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeAccel(
   156                                     JNIEnv* env, jclass jcls,
   157                                     jfloat x, jfloat y, jfloat z)
   158 {
   159     fLastAccelerometer[0] = x;
   160     fLastAccelerometer[1] = y;
   161     fLastAccelerometer[2] = z;
   162     bHasNewData = true;
   163 }
   164 
   165 // Quit
   166 extern "C" void Java_org_libsdl_app_SDLActivity_nativeQuit(
   167                                     JNIEnv* env, jclass cls)
   168 {    
   169     // Inject a SDL_QUIT event
   170     SDL_SendQuit();
   171 }
   172 
   173 // Pause
   174 extern "C" void Java_org_libsdl_app_SDLActivity_nativePause(
   175                                     JNIEnv* env, jclass cls)
   176 {
   177     if (Android_Window) {
   178         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_LOST, 0, 0);
   179         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_MINIMIZED, 0, 0);
   180     }
   181 }
   182 
   183 // Resume
   184 extern "C" void Java_org_libsdl_app_SDLActivity_nativeResume(
   185                                     JNIEnv* env, jclass cls)
   186 {
   187     if (Android_Window) {
   188         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_GAINED, 0, 0);
   189         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_RESTORED, 0, 0);
   190     }
   191 }
   192 
   193 extern "C" void Java_org_libsdl_app_SDLActivity_nativeRunAudioThread(
   194                                     JNIEnv* env, jclass cls)
   195 {
   196     /* This is the audio thread, with a different environment */
   197     mAudioEnv = env;
   198 
   199     Android_RunAudioThread();
   200 }
   201 
   202 
   203 /*******************************************************************************
   204              Functions called by SDL into Java
   205 *******************************************************************************/
   206 extern "C" SDL_bool Android_JNI_CreateContext(int majorVersion, int minorVersion)
   207 {
   208     if (mEnv->CallStaticBooleanMethod(mActivityClass, midCreateGLContext, majorVersion, minorVersion)) {
   209         return SDL_TRUE;
   210     } else {
   211         return SDL_FALSE;
   212     }
   213 }
   214 
   215 extern "C" void Android_JNI_SwapWindow()
   216 {
   217     mEnv->CallStaticVoidMethod(mActivityClass, midFlipBuffers); 
   218 }
   219 
   220 extern "C" void Android_JNI_SetActivityTitle(const char *title)
   221 {
   222     jmethodID mid;
   223 
   224     mid = mEnv->GetStaticMethodID(mActivityClass,"setActivityTitle","(Ljava/lang/String;)V");
   225     if (mid) {
   226         mEnv->CallStaticVoidMethod(mActivityClass, mid, mEnv->NewStringUTF(title));
   227     }
   228 }
   229 
   230 extern "C" SDL_bool Android_JNI_GetAccelerometerValues(float values[3])
   231 {
   232     int i;
   233     SDL_bool retval = SDL_FALSE;
   234 
   235     if (bHasNewData) {
   236         for (i = 0; i < 3; ++i) {
   237             values[i] = fLastAccelerometer[i];
   238         }
   239         bHasNewData = false;
   240         retval = SDL_TRUE;
   241     }
   242 
   243     return retval;
   244 }
   245 
   246 //
   247 // Audio support
   248 //
   249 static jboolean audioBuffer16Bit = JNI_FALSE;
   250 static jboolean audioBufferStereo = JNI_FALSE;
   251 static jobject audioBuffer = NULL;
   252 static void* audioBufferPinned = NULL;
   253 
   254 extern "C" int Android_JNI_OpenAudioDevice(int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
   255 {
   256     int audioBufferFrames;
   257 
   258     int status;
   259     JNIEnv *env;
   260     static bool isAttached = false;    
   261     status = mJavaVM->GetEnv((void **) &env, JNI_VERSION_1_4);
   262     if(status < 0) {
   263         LOGE("callback_handler: failed to get JNI environment, assuming native thread");
   264         status = mJavaVM->AttachCurrentThread(&env, NULL);
   265         if(status < 0) {
   266             LOGE("callback_handler: failed to attach current thread");
   267             return 0;
   268         }
   269         isAttached = true;
   270     }
   271 
   272     
   273     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device");
   274     audioBuffer16Bit = is16Bit;
   275     audioBufferStereo = channelCount > 1;
   276 
   277     audioBuffer = env->CallStaticObjectMethod(mActivityClass, midAudioInit, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames);
   278 
   279     if (audioBuffer == NULL) {
   280         __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: didn't get back a good audio buffer!");
   281         return 0;
   282     }
   283     audioBuffer = env->NewGlobalRef(audioBuffer);
   284 
   285     jboolean isCopy = JNI_FALSE;
   286     if (audioBuffer16Bit) {
   287         audioBufferPinned = env->GetShortArrayElements((jshortArray)audioBuffer, &isCopy);
   288         audioBufferFrames = env->GetArrayLength((jshortArray)audioBuffer);
   289     } else {
   290         audioBufferPinned = env->GetByteArrayElements((jbyteArray)audioBuffer, &isCopy);
   291         audioBufferFrames = env->GetArrayLength((jbyteArray)audioBuffer);
   292     }
   293     if (audioBufferStereo) {
   294         audioBufferFrames /= 2;
   295     }
   296  
   297     if (isAttached) {
   298         mJavaVM->DetachCurrentThread();
   299     }
   300 
   301     return audioBufferFrames;
   302 }
   303 
   304 extern "C" void * Android_JNI_GetAudioBuffer()
   305 {
   306     return audioBufferPinned;
   307 }
   308 
   309 extern "C" void Android_JNI_WriteAudioBuffer()
   310 {
   311     if (audioBuffer16Bit) {
   312         mAudioEnv->ReleaseShortArrayElements((jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
   313         mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
   314     } else {
   315         mAudioEnv->ReleaseByteArrayElements((jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT);
   316         mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer);
   317     }
   318 
   319     /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
   320 }
   321 
   322 extern "C" void Android_JNI_CloseAudioDevice()
   323 {
   324     int status;
   325     JNIEnv *env;
   326     static bool isAttached = false;    
   327     status = mJavaVM->GetEnv((void **) &env, JNI_VERSION_1_4);
   328     if(status < 0) {
   329         LOGE("callback_handler: failed to get JNI environment, assuming native thread");
   330         status = mJavaVM->AttachCurrentThread(&env, NULL);
   331         if(status < 0) {
   332             LOGE("callback_handler: failed to attach current thread");
   333             return;
   334         }
   335         isAttached = true;
   336     }
   337 
   338     env->CallStaticVoidMethod(mActivityClass, midAudioQuit); 
   339 
   340     if (audioBuffer) {
   341         env->DeleteGlobalRef(audioBuffer);
   342         audioBuffer = NULL;
   343         audioBufferPinned = NULL;
   344     }
   345 
   346     if (isAttached) {
   347         mJavaVM->DetachCurrentThread();
   348     }
   349 }
   350 
   351 // Test for an exception and call SDL_SetError with its detail if one occurs
   352 static bool Android_JNI_ExceptionOccurred()
   353 {
   354     jthrowable exception = mEnv->ExceptionOccurred();
   355     if (exception != NULL) {
   356         jmethodID mid;
   357 
   358         // Until this happens most JNI operations have undefined behaviour
   359         mEnv->ExceptionClear();
   360 
   361         jclass exceptionClass = mEnv->GetObjectClass(exception);
   362         jclass classClass = mEnv->FindClass("java/lang/Class");
   363 
   364         mid = mEnv->GetMethodID(classClass, "getName", "()Ljava/lang/String;");
   365         jstring exceptionName = (jstring)mEnv->CallObjectMethod(exceptionClass, mid);
   366         const char* exceptionNameUTF8 = mEnv->GetStringUTFChars(exceptionName, 0);
   367 
   368         mid = mEnv->GetMethodID(exceptionClass, "getMessage", "()Ljava/lang/String;");
   369         jstring exceptionMessage = (jstring)mEnv->CallObjectMethod(exception, mid);
   370 
   371         if (exceptionMessage != NULL) {
   372             const char* exceptionMessageUTF8 = mEnv->GetStringUTFChars(
   373                     exceptionMessage, 0);
   374             SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
   375             mEnv->ReleaseStringUTFChars(exceptionMessage, exceptionMessageUTF8);
   376             mEnv->DeleteLocalRef(exceptionMessage);
   377         } else {
   378             SDL_SetError("%s", exceptionNameUTF8);
   379         }
   380 
   381         mEnv->ReleaseStringUTFChars(exceptionName, exceptionNameUTF8);
   382         mEnv->DeleteLocalRef(exceptionName);
   383         mEnv->DeleteLocalRef(classClass);
   384         mEnv->DeleteLocalRef(exceptionClass);
   385         mEnv->DeleteLocalRef(exception);
   386 
   387         return true;
   388     }
   389 
   390     return false;
   391 }
   392 
   393 static int Android_JNI_FileOpen(SDL_RWops* ctx)
   394 {
   395     int result = 0;
   396 
   397     jmethodID mid;
   398     jobject context;
   399     jobject assetManager;
   400     jobject inputStream;
   401     jclass channels;
   402     jobject readableByteChannel;
   403     jstring fileNameJString;
   404 
   405     bool allocatedLocalFrame = false;
   406 
   407     if (mEnv->PushLocalFrame(16) < 0) {
   408         SDL_SetError("Failed to allocate enough JVM local references");
   409         goto failure;
   410     } else {
   411         allocatedLocalFrame = true;
   412     }
   413 
   414     fileNameJString = (jstring)ctx->hidden.androidio.fileName;
   415 
   416     // context = SDLActivity.getContext();
   417     mid = mEnv->GetStaticMethodID(mActivityClass,
   418             "getContext","()Landroid/content/Context;");
   419     context = mEnv->CallStaticObjectMethod(mActivityClass, mid);
   420 
   421     // assetManager = context.getAssets();
   422     mid = mEnv->GetMethodID(mEnv->GetObjectClass(context),
   423             "getAssets", "()Landroid/content/res/AssetManager;");
   424     assetManager = mEnv->CallObjectMethod(context, mid);
   425 
   426     // inputStream = assetManager.open(<filename>);
   427     mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager),
   428             "open", "(Ljava/lang/String;)Ljava/io/InputStream;");
   429     inputStream = mEnv->CallObjectMethod(assetManager, mid, fileNameJString);
   430     if (Android_JNI_ExceptionOccurred()) {
   431         goto failure;
   432     }
   433 
   434     ctx->hidden.androidio.inputStream = inputStream;
   435     ctx->hidden.androidio.inputStreamRef = mEnv->NewGlobalRef(inputStream);
   436 
   437     // Despite all the visible documentation on [Asset]InputStream claiming
   438     // that the .available() method is not guaranteed to return the entire file
   439     // size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
   440     // android/apis/content/ReadAsset.java imply that Android's
   441     // AssetInputStream.available() /will/ always return the total file size
   442 
   443     // size = inputStream.available();
   444     mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   445             "available", "()I");
   446     ctx->hidden.androidio.size = mEnv->CallIntMethod(inputStream, mid);
   447     if (Android_JNI_ExceptionOccurred()) {
   448         goto failure;
   449     }
   450 
   451     // readableByteChannel = Channels.newChannel(inputStream);
   452     channels = mEnv->FindClass("java/nio/channels/Channels");
   453     mid = mEnv->GetStaticMethodID(channels,
   454             "newChannel",
   455             "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
   456     readableByteChannel = mEnv->CallStaticObjectMethod(
   457             channels, mid, inputStream);
   458     if (Android_JNI_ExceptionOccurred()) {
   459         goto failure;
   460     }
   461 
   462     ctx->hidden.androidio.readableByteChannel = readableByteChannel;
   463     ctx->hidden.androidio.readableByteChannelRef =
   464         mEnv->NewGlobalRef(readableByteChannel);
   465 
   466     // Store .read id for reading purposes
   467     mid = mEnv->GetMethodID(mEnv->GetObjectClass(readableByteChannel),
   468             "read", "(Ljava/nio/ByteBuffer;)I");
   469     ctx->hidden.androidio.readMethod = mid;
   470 
   471     ctx->hidden.androidio.position = 0;
   472 
   473     if (false) {
   474 failure:
   475         result = -1;
   476 
   477         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   478 
   479         if(ctx->hidden.androidio.inputStreamRef != NULL) {
   480             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   481         }
   482     }
   483 
   484     if (allocatedLocalFrame) {
   485         mEnv->PopLocalFrame(NULL);
   486     }
   487 
   488     return result;
   489 }
   490 
   491 extern "C" int Android_JNI_FileOpen(SDL_RWops* ctx,
   492         const char* fileName, const char*)
   493 {
   494     if (!ctx) {
   495         return -1;
   496     }
   497 
   498     jstring fileNameJString = mEnv->NewStringUTF(fileName);
   499     ctx->hidden.androidio.fileName = fileNameJString;
   500     ctx->hidden.androidio.fileNameRef = mEnv->NewGlobalRef(fileNameJString);
   501     ctx->hidden.androidio.inputStreamRef = NULL;
   502     mEnv->DeleteLocalRef(fileNameJString);
   503 
   504     return Android_JNI_FileOpen(ctx);
   505 }
   506 
   507 extern "C" size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
   508         size_t size, size_t maxnum)
   509 {
   510     int bytesRemaining = size * maxnum;
   511     int bytesRead = 0;
   512 
   513     jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannel;
   514     jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
   515     jobject byteBuffer = mEnv->NewDirectByteBuffer(buffer, bytesRemaining);
   516 
   517     while (bytesRemaining > 0) {
   518         // result = readableByteChannel.read(...);
   519         int result = mEnv->CallIntMethod(readableByteChannel, readMethod, byteBuffer);
   520 
   521         if (Android_JNI_ExceptionOccurred()) {
   522             mEnv->DeleteLocalRef(byteBuffer);
   523             return 0;
   524         }
   525 
   526         if (result < 0) {
   527             break;
   528         }
   529 
   530         bytesRemaining -= result;
   531         bytesRead += result;
   532         ctx->hidden.androidio.position += result;
   533     }
   534 
   535     mEnv->DeleteLocalRef(byteBuffer);
   536 
   537     return bytesRead / size;
   538 }
   539 
   540 extern "C" size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
   541         size_t size, size_t num)
   542 {
   543     SDL_SetError("Cannot write to Android package filesystem");
   544     return 0;
   545 }
   546 
   547 static int Android_JNI_FileClose(SDL_RWops* ctx, bool release)
   548 {
   549     int result = 0;
   550 
   551     if (ctx) {
   552         if (release) {
   553             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   554         }
   555 
   556         jobject inputStream = (jobject)ctx->hidden.androidio.inputStream;
   557 
   558         // inputStream.close();
   559         jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   560                 "close", "()V");
   561         mEnv->CallVoidMethod(inputStream, mid);
   562         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   563         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
   564         if (Android_JNI_ExceptionOccurred()) {
   565             result = -1;
   566         }
   567 
   568         if (release) {
   569             SDL_FreeRW(ctx);
   570         }
   571     }
   572 
   573     return result;
   574 }
   575 
   576 
   577 extern "C" long Android_JNI_FileSeek(SDL_RWops* ctx, long offset, int whence)
   578 {
   579     long newPosition;
   580 
   581     switch (whence) {
   582         case RW_SEEK_SET:
   583             newPosition = offset;
   584             break;
   585         case RW_SEEK_CUR:
   586             newPosition = ctx->hidden.androidio.position + offset;
   587             break;
   588         case RW_SEEK_END:
   589             newPosition = ctx->hidden.androidio.size + offset;
   590             break;
   591         default:
   592             SDL_SetError("Unknown value for 'whence'");
   593             return -1;
   594     }
   595     if (newPosition < 0) {
   596         newPosition = 0;
   597     }
   598     if (newPosition > ctx->hidden.androidio.size) {
   599         newPosition = ctx->hidden.androidio.size;
   600     }
   601 
   602     long movement = newPosition - ctx->hidden.androidio.position;
   603     jobject inputStream = (jobject)ctx->hidden.androidio.inputStream;
   604 
   605     if (movement > 0) {
   606         unsigned char buffer[1024];
   607 
   608         // The easy case where we're seeking forwards
   609         while (movement > 0) {
   610             long amount = (long) sizeof (buffer);
   611             if (amount > movement) {
   612                 amount = movement;
   613             }
   614             size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount);
   615 
   616             if (result <= 0) {
   617                 // Failed to read/skip the required amount, so fail
   618                 return -1;
   619             }
   620 
   621             movement -= result;
   622         }
   623     } else if (movement < 0) {
   624         // We can't seek backwards so we have to reopen the file and seek
   625         // forwards which obviously isn't very efficient
   626         Android_JNI_FileClose(ctx, false);
   627         Android_JNI_FileOpen(ctx);
   628         Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
   629     }
   630 
   631     ctx->hidden.androidio.position = newPosition;
   632 
   633     return ctx->hidden.androidio.position;
   634 }
   635 
   636 extern "C" int Android_JNI_FileClose(SDL_RWops* ctx)
   637 {
   638     return Android_JNI_FileClose(ctx, true);
   639 }
   640 
   641 #endif /* __ANDROID__ */
   642 
   643 /* vi: set ts=4 sw=4 expandtab: */