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