src/core/android/SDL_android.cpp
author Ryan C. Gordon <icculus@icculus.org>
Sat, 15 Oct 2011 23:50:06 -0700
changeset 5996 102a9ec1ea13
parent 5994 153d15ab3032
child 6044 35448a5ea044
permissions -rw-r--r--
Don't use a global JNIEnv across threads; it's not thread safe.

Obtain the correct environment in a thread-safe way when appropriate instead.

Fixes Bugzilla #1312.

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