src/core/android/SDL_android.cpp
author Sam Lantinga <slouken@libsdl.org>
Sat, 31 Dec 2011 09:28:07 -0500
changeset 6138 4c64952a58fb
parent 6044 35448a5ea044
child 6186 e565ac981de6
permissions -rwxr-xr-x
Happy New Year!
     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 /* Impelemented 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 = 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 extern "C" void Java_org_libsdl_app_SDLActivity_nativeRunAudioThread(
   171                                     JNIEnv* env, jclass cls)
   172 {
   173     /* This is the audio thread, with a different environment */
   174     mAudioEnv = env;
   175 
   176     Android_RunAudioThread();
   177 }
   178 
   179 
   180 /*******************************************************************************
   181              Functions called by SDL into Java
   182 *******************************************************************************/
   183 extern "C" SDL_bool Android_JNI_CreateContext(int majorVersion, int minorVersion)
   184 {
   185     if (mEnv->CallStaticBooleanMethod(mActivityClass, midCreateGLContext, majorVersion, minorVersion)) {
   186         return SDL_TRUE;
   187     } else {
   188         return SDL_FALSE;
   189     }
   190 }
   191 
   192 extern "C" void Android_JNI_SwapWindow()
   193 {
   194     mEnv->CallStaticVoidMethod(mActivityClass, midFlipBuffers); 
   195 }
   196 
   197 extern "C" void Android_JNI_SetActivityTitle(const char *title)
   198 {
   199     jmethodID mid;
   200 
   201     mid = mEnv->GetStaticMethodID(mActivityClass,"setActivityTitle","(Ljava/lang/String;)V");
   202     if (mid) {
   203         mEnv->CallStaticVoidMethod(mActivityClass, mid, mEnv->NewStringUTF(title));
   204     }
   205 }
   206 
   207 extern "C" void Android_JNI_GetAccelerometerValues(float values[3])
   208 {
   209     int i;
   210     for (i = 0; i < 3; ++i) {
   211         values[i] = fLastAccelerometer[i];
   212     }
   213 }
   214 
   215 //
   216 // Audio support
   217 //
   218 static jboolean audioBuffer16Bit = JNI_FALSE;
   219 static jboolean audioBufferStereo = JNI_FALSE;
   220 static jobject audioBuffer = NULL;
   221 static void* audioBufferPinned = NULL;
   222 
   223 extern "C" int Android_JNI_OpenAudioDevice(int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
   224 {
   225     int audioBufferFrames;
   226 
   227     int status;
   228     JNIEnv *env;
   229     static bool isAttached = false;    
   230     status = mJavaVM->GetEnv((void **) &env, JNI_VERSION_1_4);
   231     if(status < 0) {
   232         LOGE("callback_handler: failed to get JNI environment, assuming native thread");
   233         status = mJavaVM->AttachCurrentThread(&env, NULL);
   234         if(status < 0) {
   235             LOGE("callback_handler: failed to attach current thread");
   236             return 0;
   237         }
   238         isAttached = true;
   239     }
   240 
   241     
   242     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device");
   243     audioBuffer16Bit = is16Bit;
   244     audioBufferStereo = channelCount > 1;
   245 
   246     audioBuffer = env->CallStaticObjectMethod(mActivityClass, midAudioInit, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames);
   247 
   248     if (audioBuffer == NULL) {
   249         __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: didn't get back a good audio buffer!");
   250         return 0;
   251     }
   252     audioBuffer = env->NewGlobalRef(audioBuffer);
   253 
   254     jboolean isCopy = JNI_FALSE;
   255     if (audioBuffer16Bit) {
   256         audioBufferPinned = env->GetShortArrayElements((jshortArray)audioBuffer, &isCopy);
   257         audioBufferFrames = env->GetArrayLength((jshortArray)audioBuffer);
   258     } else {
   259         audioBufferPinned = env->GetByteArrayElements((jbyteArray)audioBuffer, &isCopy);
   260         audioBufferFrames = env->GetArrayLength((jbyteArray)audioBuffer);
   261     }
   262     if (audioBufferStereo) {
   263         audioBufferFrames /= 2;
   264     }
   265  
   266     if (isAttached) {
   267         mJavaVM->DetachCurrentThread();
   268     }
   269 
   270     return audioBufferFrames;
   271 }
   272 
   273 extern "C" void * Android_JNI_GetAudioBuffer()
   274 {
   275     return audioBufferPinned;
   276 }
   277 
   278 extern "C" void Android_JNI_WriteAudioBuffer()
   279 {
   280     if (audioBuffer16Bit) {
   281         mAudioEnv->ReleaseShortArrayElements((jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
   282         mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
   283     } else {
   284         mAudioEnv->ReleaseByteArrayElements((jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT);
   285         mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer);
   286     }
   287 
   288     /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
   289 }
   290 
   291 extern "C" void Android_JNI_CloseAudioDevice()
   292 {
   293     int status;
   294     JNIEnv *env;
   295     static bool isAttached = false;    
   296     status = mJavaVM->GetEnv((void **) &env, JNI_VERSION_1_4);
   297     if(status < 0) {
   298         LOGE("callback_handler: failed to get JNI environment, assuming native thread");
   299         status = mJavaVM->AttachCurrentThread(&env, NULL);
   300         if(status < 0) {
   301             LOGE("callback_handler: failed to attach current thread");
   302             return;
   303         }
   304         isAttached = true;
   305     }
   306 
   307     env->CallStaticVoidMethod(mActivityClass, midAudioQuit); 
   308 
   309     if (audioBuffer) {
   310         env->DeleteGlobalRef(audioBuffer);
   311         audioBuffer = NULL;
   312         audioBufferPinned = NULL;
   313     }
   314 
   315     if (isAttached) {
   316         mJavaVM->DetachCurrentThread();
   317     }
   318 }
   319 
   320 // Test for an exception and call SDL_SetError with its detail if one occurs
   321 static bool Android_JNI_ExceptionOccurred()
   322 {
   323     jthrowable exception = mEnv->ExceptionOccurred();
   324     if (exception != NULL) {
   325         jmethodID mid;
   326 
   327         // Until this happens most JNI operations have undefined behaviour
   328         mEnv->ExceptionClear();
   329 
   330         jclass exceptionClass = mEnv->GetObjectClass(exception);
   331         jclass classClass = mEnv->FindClass("java/lang/Class");
   332 
   333         mid = mEnv->GetMethodID(classClass, "getName", "()Ljava/lang/String;");
   334         jstring exceptionName = (jstring)mEnv->CallObjectMethod(exceptionClass, mid);
   335         const char* exceptionNameUTF8 = mEnv->GetStringUTFChars(exceptionName, 0);
   336 
   337         mid = mEnv->GetMethodID(exceptionClass, "getMessage", "()Ljava/lang/String;");
   338         jstring exceptionMessage = (jstring)mEnv->CallObjectMethod(exception, mid);
   339 
   340         if (exceptionMessage != NULL) {
   341             const char* exceptionMessageUTF8 = mEnv->GetStringUTFChars(
   342                     exceptionMessage, 0);
   343             SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
   344             mEnv->ReleaseStringUTFChars(exceptionMessage, exceptionMessageUTF8);
   345             mEnv->DeleteLocalRef(exceptionMessage);
   346         } else {
   347             SDL_SetError("%s", exceptionNameUTF8);
   348         }
   349 
   350         mEnv->ReleaseStringUTFChars(exceptionName, exceptionNameUTF8);
   351         mEnv->DeleteLocalRef(exceptionName);
   352         mEnv->DeleteLocalRef(classClass);
   353         mEnv->DeleteLocalRef(exceptionClass);
   354         mEnv->DeleteLocalRef(exception);
   355 
   356         return true;
   357     }
   358 
   359     return false;
   360 }
   361 
   362 static int Android_JNI_FileOpen(SDL_RWops* ctx)
   363 {
   364     int result = 0;
   365 
   366     jmethodID mid;
   367     jobject context;
   368     jobject assetManager;
   369     jobject inputStream;
   370     jclass channels;
   371     jobject readableByteChannel;
   372     jstring fileNameJString;
   373 
   374     bool allocatedLocalFrame = false;
   375 
   376     if (mEnv->PushLocalFrame(16) < 0) {
   377         SDL_SetError("Failed to allocate enough JVM local references");
   378         goto failure;
   379     } else {
   380         allocatedLocalFrame = true;
   381     }
   382 
   383     fileNameJString = (jstring)ctx->hidden.androidio.fileName;
   384 
   385     // context = SDLActivity.getContext();
   386     mid = mEnv->GetStaticMethodID(mActivityClass,
   387             "getContext","()Landroid/content/Context;");
   388     context = mEnv->CallStaticObjectMethod(mActivityClass, mid);
   389 
   390     // assetManager = context.getAssets();
   391     mid = mEnv->GetMethodID(mEnv->GetObjectClass(context),
   392             "getAssets", "()Landroid/content/res/AssetManager;");
   393     assetManager = mEnv->CallObjectMethod(context, mid);
   394 
   395     // inputStream = assetManager.open(<filename>);
   396     mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager),
   397             "open", "(Ljava/lang/String;)Ljava/io/InputStream;");
   398     inputStream = mEnv->CallObjectMethod(assetManager, mid, fileNameJString);
   399     if (Android_JNI_ExceptionOccurred()) {
   400         goto failure;
   401     }
   402 
   403     ctx->hidden.androidio.inputStream = inputStream;
   404     ctx->hidden.androidio.inputStreamRef = mEnv->NewGlobalRef(inputStream);
   405 
   406     // Despite all the visible documentation on [Asset]InputStream claiming
   407     // that the .available() method is not guaranteed to return the entire file
   408     // size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
   409     // android/apis/content/ReadAsset.java imply that Android's
   410     // AssetInputStream.available() /will/ always return the total file size
   411 
   412     // size = inputStream.available();
   413     mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   414             "available", "()I");
   415     ctx->hidden.androidio.size = mEnv->CallIntMethod(inputStream, mid);
   416     if (Android_JNI_ExceptionOccurred()) {
   417         goto failure;
   418     }
   419 
   420     // readableByteChannel = Channels.newChannel(inputStream);
   421     channels = mEnv->FindClass("java/nio/channels/Channels");
   422     mid = mEnv->GetStaticMethodID(channels,
   423             "newChannel",
   424             "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
   425     readableByteChannel = mEnv->CallStaticObjectMethod(
   426             channels, mid, inputStream);
   427     if (Android_JNI_ExceptionOccurred()) {
   428         goto failure;
   429     }
   430 
   431     ctx->hidden.androidio.readableByteChannel = readableByteChannel;
   432     ctx->hidden.androidio.readableByteChannelRef =
   433         mEnv->NewGlobalRef(readableByteChannel);
   434 
   435     // Store .read id for reading purposes
   436     mid = mEnv->GetMethodID(mEnv->GetObjectClass(readableByteChannel),
   437             "read", "(Ljava/nio/ByteBuffer;)I");
   438     ctx->hidden.androidio.readMethod = mid;
   439 
   440     ctx->hidden.androidio.position = 0;
   441 
   442     if (false) {
   443 failure:
   444         result = -1;
   445 
   446         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   447 
   448         if(ctx->hidden.androidio.inputStreamRef != NULL) {
   449             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   450         }
   451     }
   452 
   453     if (allocatedLocalFrame) {
   454         mEnv->PopLocalFrame(NULL);
   455     }
   456 
   457     return result;
   458 }
   459 
   460 extern "C" int Android_JNI_FileOpen(SDL_RWops* ctx,
   461         const char* fileName, const char*)
   462 {
   463     if (!ctx) {
   464         return -1;
   465     }
   466 
   467     jstring fileNameJString = mEnv->NewStringUTF(fileName);
   468     ctx->hidden.androidio.fileName = fileNameJString;
   469     ctx->hidden.androidio.fileNameRef = mEnv->NewGlobalRef(fileNameJString);
   470     ctx->hidden.androidio.inputStreamRef = NULL;
   471     mEnv->DeleteLocalRef(fileNameJString);
   472 
   473     return Android_JNI_FileOpen(ctx);
   474 }
   475 
   476 extern "C" size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
   477         size_t size, size_t maxnum)
   478 {
   479     int bytesRemaining = size * maxnum;
   480     int bytesRead = 0;
   481 
   482     jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannel;
   483     jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
   484     jobject byteBuffer = mEnv->NewDirectByteBuffer(buffer, bytesRemaining);
   485 
   486     while (bytesRemaining > 0) {
   487         // result = readableByteChannel.read(...);
   488         int result = mEnv->CallIntMethod(readableByteChannel, readMethod, byteBuffer);
   489 
   490         if (Android_JNI_ExceptionOccurred()) {
   491             mEnv->DeleteLocalRef(byteBuffer);
   492             return 0;
   493         }
   494 
   495         if (result < 0) {
   496             break;
   497         }
   498 
   499         bytesRemaining -= result;
   500         bytesRead += result;
   501         ctx->hidden.androidio.position += result;
   502     }
   503 
   504     mEnv->DeleteLocalRef(byteBuffer);
   505 
   506     return bytesRead / size;
   507 }
   508 
   509 extern "C" size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
   510         size_t size, size_t num)
   511 {
   512     SDL_SetError("Cannot write to Android package filesystem");
   513     return 0;
   514 }
   515 
   516 static int Android_JNI_FileClose(SDL_RWops* ctx, bool release)
   517 {
   518     int result = 0;
   519 
   520     if (ctx) {
   521         if (release) {
   522             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   523         }
   524 
   525         jobject inputStream = (jobject)ctx->hidden.androidio.inputStream;
   526 
   527         // inputStream.close();
   528         jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   529                 "close", "()V");
   530         mEnv->CallVoidMethod(inputStream, mid);
   531         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   532         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
   533         if (Android_JNI_ExceptionOccurred()) {
   534             result = -1;
   535         }
   536 
   537         if (release) {
   538             SDL_FreeRW(ctx);
   539         }
   540     }
   541 
   542     return result;
   543 }
   544 
   545 
   546 extern "C" long Android_JNI_FileSeek(SDL_RWops* ctx, long offset, int whence)
   547 {
   548     long newPosition;
   549 
   550     switch (whence) {
   551         case RW_SEEK_SET:
   552             newPosition = offset;
   553             break;
   554         case RW_SEEK_CUR:
   555             newPosition = ctx->hidden.androidio.position + offset;
   556             break;
   557         case RW_SEEK_END:
   558             newPosition = ctx->hidden.androidio.size + offset;
   559             break;
   560         default:
   561             SDL_SetError("Unknown value for 'whence'");
   562             return -1;
   563     }
   564     if (newPosition < 0) {
   565         newPosition = 0;
   566     }
   567     if (newPosition > ctx->hidden.androidio.size) {
   568         newPosition = ctx->hidden.androidio.size;
   569     }
   570 
   571     long movement = newPosition - ctx->hidden.androidio.position;
   572     jobject inputStream = (jobject)ctx->hidden.androidio.inputStream;
   573 
   574     if (movement > 0) {
   575         unsigned char buffer[1024];
   576 
   577         // The easy case where we're seeking forwards
   578         while (movement > 0) {
   579             long amount = (long) sizeof (buffer);
   580             if (amount > movement) {
   581                 amount = movement;
   582             }
   583             size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount);
   584 
   585             if (result <= 0) {
   586                 // Failed to read/skip the required amount, so fail
   587                 return -1;
   588             }
   589 
   590             movement -= result;
   591         }
   592     } else if (movement < 0) {
   593         // We can't seek backwards so we have to reopen the file and seek
   594         // forwards which obviously isn't very efficient
   595         Android_JNI_FileClose(ctx, false);
   596         Android_JNI_FileOpen(ctx);
   597         Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
   598     }
   599 
   600     ctx->hidden.androidio.position = newPosition;
   601 
   602     return ctx->hidden.androidio.position;
   603 }
   604 
   605 extern "C" int Android_JNI_FileClose(SDL_RWops* ctx)
   606 {
   607     return Android_JNI_FileClose(ctx, true);
   608 }
   609 
   610 #endif /* __ANDROID__ */
   611 
   612 /* vi: set ts=4 sw=4 expandtab: */