src/core/android/SDL_android.cpp
author Ryan C. Gordon <icculus@icculus.org>
Fri, 29 Jul 2011 16:51:25 -0400
changeset 5582 1281a3f1f0a6
parent 5535 96594ac5fd1a
child 5650 640c67302f8e
permissions -rw-r--r--
Allow Android platforms to read from .apk files via the RWOPS interface.

Fixes Bugzilla #1261.

Thanks to Tim Angus 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 /* Impelemented in audio/android/SDL_androidaudio.c */
    33 extern void Android_RunAudioThread();
    34 } // C
    35 
    36 /*******************************************************************************
    37  This file links the Java side of Android with libsdl
    38 *******************************************************************************/
    39 #include <jni.h>
    40 #include <android/log.h>
    41 
    42 
    43 /*******************************************************************************
    44                                Globals
    45 *******************************************************************************/
    46 static JNIEnv* mEnv = NULL;
    47 static JNIEnv* mAudioEnv = NULL;
    48 
    49 // Main activity
    50 static jclass mActivityClass;
    51 
    52 // method signatures
    53 static jmethodID midCreateGLContext;
    54 static jmethodID midFlipBuffers;
    55 static jmethodID midAudioInit;
    56 static jmethodID midAudioWriteShortBuffer;
    57 static jmethodID midAudioWriteByteBuffer;
    58 static jmethodID midAudioQuit;
    59 
    60 // Accelerometer data storage
    61 static float fLastAccelerometer[3];
    62 
    63 
    64 /*******************************************************************************
    65                  Functions called by JNI
    66 *******************************************************************************/
    67 
    68 // Library init
    69 extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
    70 {
    71     return JNI_VERSION_1_4;
    72 }
    73 
    74 // Called before SDL_main() to initialize JNI bindings
    75 extern "C" void SDL_Android_Init(JNIEnv* env, jclass cls)
    76 {
    77     __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init()");
    78 
    79     mEnv = env;
    80     mActivityClass = cls;
    81 
    82     midCreateGLContext = mEnv->GetStaticMethodID(mActivityClass,
    83                                 "createGLContext","(II)Z");
    84     midFlipBuffers = mEnv->GetStaticMethodID(mActivityClass,
    85                                 "flipBuffers","()V");
    86     midAudioInit = mEnv->GetStaticMethodID(mActivityClass, 
    87                                 "audioInit", "(IZZI)Ljava/lang/Object;");
    88     midAudioWriteShortBuffer = mEnv->GetStaticMethodID(mActivityClass,
    89                                 "audioWriteShortBuffer", "([S)V");
    90     midAudioWriteByteBuffer = mEnv->GetStaticMethodID(mActivityClass,
    91                                 "audioWriteByteBuffer", "([B)V");
    92     midAudioQuit = mEnv->GetStaticMethodID(mActivityClass,
    93                                 "audioQuit", "()V");
    94 
    95     if(!midCreateGLContext || !midFlipBuffers || !midAudioInit ||
    96        !midAudioWriteShortBuffer || !midAudioWriteByteBuffer || !midAudioQuit) {
    97         __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL: Couldn't locate Java callbacks, check that they're named and typed correctly");
    98     }
    99 }
   100 
   101 // Resize
   102 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeResize(
   103                                     JNIEnv* env, jclass jcls,
   104                                     jint width, jint height, jint format)
   105 {
   106     Android_SetScreenResolution(width, height, format);
   107 }
   108 
   109 // Keydown
   110 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyDown(
   111                                     JNIEnv* env, jclass jcls, jint keycode)
   112 {
   113     Android_OnKeyDown(keycode);
   114 }
   115 
   116 // Keyup
   117 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyUp(
   118                                     JNIEnv* env, jclass jcls, jint keycode)
   119 {
   120     Android_OnKeyUp(keycode);
   121 }
   122 
   123 // Touch
   124 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeTouch(
   125                                     JNIEnv* env, jclass jcls,
   126                                     jint action, jfloat x, jfloat y, jfloat p)
   127 {
   128     Android_OnTouch(action, x, y, p);
   129 }
   130 
   131 // Accelerometer
   132 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeAccel(
   133                                     JNIEnv* env, jclass jcls,
   134                                     jfloat x, jfloat y, jfloat z)
   135 {
   136     fLastAccelerometer[0] = x;
   137     fLastAccelerometer[1] = y;
   138     fLastAccelerometer[2] = z;   
   139 }
   140 
   141 // Quit
   142 extern "C" void Java_org_libsdl_app_SDLActivity_nativeQuit(
   143                                     JNIEnv* env, jclass cls)
   144 {    
   145     // Inject a SDL_QUIT event
   146     SDL_SendQuit();
   147 }
   148 
   149 extern "C" void Java_org_libsdl_app_SDLActivity_nativeRunAudioThread(
   150                                     JNIEnv* env, jclass cls)
   151 {
   152     /* This is the audio thread, with a different environment */
   153     mAudioEnv = env;
   154 
   155     Android_RunAudioThread();
   156 }
   157 
   158 
   159 /*******************************************************************************
   160              Functions called by SDL into Java
   161 *******************************************************************************/
   162 extern "C" SDL_bool Android_JNI_CreateContext(int majorVersion, int minorVersion)
   163 {
   164     if (mEnv->CallStaticBooleanMethod(mActivityClass, midCreateGLContext, majorVersion, minorVersion)) {
   165         return SDL_TRUE;
   166     } else {
   167         return SDL_FALSE;
   168     }
   169 }
   170 
   171 extern "C" void Android_JNI_SwapWindow()
   172 {
   173     mEnv->CallStaticVoidMethod(mActivityClass, midFlipBuffers); 
   174 }
   175 
   176 extern "C" void Android_JNI_SetActivityTitle(const char *title)
   177 {
   178     jmethodID mid;
   179 
   180     mid = mEnv->GetStaticMethodID(mActivityClass,"setActivityTitle","(Ljava/lang/String;)V");
   181     if (mid) {
   182         mEnv->CallStaticVoidMethod(mActivityClass, mid, mEnv->NewStringUTF(title));
   183     }
   184 }
   185 
   186 extern "C" void Android_JNI_GetAccelerometerValues(float values[3])
   187 {
   188     int i;
   189     for (i = 0; i < 3; ++i) {
   190         values[i] = fLastAccelerometer[i];
   191     }
   192 }
   193 
   194 //
   195 // Audio support
   196 //
   197 static jboolean audioBuffer16Bit = JNI_FALSE;
   198 static jboolean audioBufferStereo = JNI_FALSE;
   199 static jobject audioBuffer = NULL;
   200 static void* audioBufferPinned = NULL;
   201 
   202 extern "C" int Android_JNI_OpenAudioDevice(int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
   203 {
   204     int audioBufferFrames;
   205 
   206     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device");
   207     audioBuffer16Bit = is16Bit;
   208     audioBufferStereo = channelCount > 1;
   209 
   210     audioBuffer = mEnv->CallStaticObjectMethod(mActivityClass, midAudioInit, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames);
   211 
   212     if (audioBuffer == NULL) {
   213         __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: didn't get back a good audio buffer!");
   214         return 0;
   215     }
   216     audioBuffer = mEnv->NewGlobalRef(audioBuffer);
   217 
   218     jboolean isCopy = JNI_FALSE;
   219     if (audioBuffer16Bit) {
   220         audioBufferPinned = mEnv->GetShortArrayElements((jshortArray)audioBuffer, &isCopy);
   221         audioBufferFrames = mEnv->GetArrayLength((jshortArray)audioBuffer);
   222     } else {
   223         audioBufferPinned = mEnv->GetByteArrayElements((jbyteArray)audioBuffer, &isCopy);
   224         audioBufferFrames = mEnv->GetArrayLength((jbyteArray)audioBuffer);
   225     }
   226     if (audioBufferStereo) {
   227         audioBufferFrames /= 2;
   228     }
   229 
   230     return audioBufferFrames;
   231 }
   232 
   233 extern "C" void * Android_JNI_GetAudioBuffer()
   234 {
   235     return audioBufferPinned;
   236 }
   237 
   238 extern "C" void Android_JNI_WriteAudioBuffer()
   239 {
   240     if (audioBuffer16Bit) {
   241         mAudioEnv->ReleaseShortArrayElements((jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
   242         mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
   243     } else {
   244         mAudioEnv->ReleaseByteArrayElements((jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT);
   245         mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer);
   246     }
   247 
   248     /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
   249 }
   250 
   251 extern "C" void Android_JNI_CloseAudioDevice()
   252 {
   253     mEnv->CallStaticVoidMethod(mActivityClass, midAudioQuit); 
   254 
   255     if (audioBuffer) {
   256         mEnv->DeleteGlobalRef(audioBuffer);
   257         audioBuffer = NULL;
   258         audioBufferPinned = NULL;
   259     }
   260 }
   261 
   262 static int Android_JNI_FileOpen(SDL_RWops* ctx)
   263 {
   264     jstring fileNameJString = (jstring)ctx->hidden.androidio.fileName;
   265 
   266     // context = SDLActivity.getContext();
   267     jmethodID mid = mEnv->GetStaticMethodID(mActivityClass,
   268             "getContext","()Landroid/content/Context;");
   269     jobject context = mEnv->CallStaticObjectMethod(mActivityClass, mid);
   270 
   271     // assetManager = context.getAssets();
   272     mid = mEnv->GetMethodID(mEnv->GetObjectClass(context),
   273             "getAssets","()Landroid/content/res/AssetManager;");
   274     jobject assetManager = mEnv->CallObjectMethod(context, mid);
   275 
   276     // inputStream = assetManager.open(<filename>);
   277     mEnv->ExceptionClear();
   278     mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager),
   279             "open", "(Ljava/lang/String;)Ljava/io/InputStream;");
   280     jobject inputStream = mEnv->CallObjectMethod(assetManager, mid, fileNameJString);
   281     if (mEnv->ExceptionOccurred()) {
   282         mEnv->ExceptionDescribe();
   283         mEnv->ExceptionClear();
   284         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   285         return -1;
   286     } else {
   287         ctx->hidden.androidio.inputStream = inputStream;
   288         ctx->hidden.androidio.inputStreamRef = mEnv->NewGlobalRef(inputStream);
   289     }
   290 
   291     // Store .skip id for seeking purposes
   292     mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   293             "skip", "(J)J");
   294     ctx->hidden.androidio.skipMethod = mid;
   295 
   296     // Despite all the visible documentation on [Asset]InputStream claiming
   297     // that the .available() method is not guaranteed to return the entire file
   298     // size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
   299     // android/apis/content/ReadAsset.java imply that Android's
   300     // AssetInputStream.available() /will/ always return the total file size
   301 
   302     // size = inputStream.available();
   303     mEnv->ExceptionClear();
   304     mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   305             "available", "()I");
   306     ctx->hidden.androidio.size = mEnv->CallIntMethod(inputStream, mid);
   307     if (mEnv->ExceptionOccurred()) {
   308         mEnv->ExceptionDescribe();
   309         mEnv->ExceptionClear();
   310         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   311         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   312         return -1;
   313     }
   314 
   315     // readableByteChannel = Channels.newChannel(inputStream);
   316     mEnv->ExceptionClear();
   317     jclass channels = mEnv->FindClass("java/nio/channels/Channels");
   318     mid = mEnv->GetStaticMethodID(channels,
   319             "newChannel",
   320             "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
   321     jobject readableByteChannel = mEnv->CallStaticObjectMethod(
   322             channels, mid, inputStream);
   323     if (mEnv->ExceptionOccurred()) {
   324         mEnv->ExceptionDescribe();
   325         mEnv->ExceptionClear();
   326         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   327         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   328         return -1;
   329     } else {
   330         ctx->hidden.androidio.readableByteChannel = readableByteChannel;
   331         ctx->hidden.androidio.readableByteChannelRef =
   332             mEnv->NewGlobalRef(readableByteChannel);
   333     }
   334 
   335     // Store .read id for reading purposes
   336     mid = mEnv->GetMethodID(mEnv->GetObjectClass(readableByteChannel),
   337             "read", "(Ljava/nio/ByteBuffer;)I");
   338     ctx->hidden.androidio.readMethod = mid;
   339 
   340     ctx->hidden.androidio.position = 0;
   341 
   342     return 0;
   343 }
   344 
   345 extern "C" int Android_JNI_FileOpen(SDL_RWops* ctx,
   346         const char* fileName, const char*)
   347 {
   348     if (!ctx) {
   349         return -1;
   350     }
   351 
   352     jstring fileNameJString = mEnv->NewStringUTF(fileName);
   353     ctx->hidden.androidio.fileName = fileNameJString;
   354     ctx->hidden.androidio.fileNameRef = mEnv->NewGlobalRef(fileNameJString);
   355 
   356     return Android_JNI_FileOpen(ctx);
   357 }
   358 
   359 extern "C" size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
   360         size_t size, size_t maxnum)
   361 {
   362     int bytesRemaining = size * maxnum;
   363     int bytesRead = 0;
   364 
   365     jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannel;
   366     jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
   367     jobject byteBuffer = mEnv->NewDirectByteBuffer(buffer, bytesRemaining);
   368 
   369     mEnv->ExceptionClear();
   370     while (bytesRemaining > 0) {
   371         // result = readableByteChannel.read(...);
   372         int result = mEnv->CallIntMethod(readableByteChannel, readMethod, byteBuffer);
   373 
   374         if (mEnv->ExceptionOccurred()) {
   375             mEnv->ExceptionDescribe();
   376             mEnv->ExceptionClear();
   377             return 0;
   378         }
   379 
   380         if (result < 0) {
   381             break;
   382         }
   383 
   384         bytesRemaining -= result;
   385         bytesRead += result;
   386         ctx->hidden.androidio.position += result;
   387     }
   388 
   389     return bytesRead / size;
   390 }
   391 
   392 extern "C" size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
   393         size_t size, size_t num)
   394 {
   395     SDL_SetError("Cannot write to Android package filesystem");
   396     return 0;
   397 }
   398 
   399 static int Android_JNI_FileClose(SDL_RWops* ctx, bool release)
   400 {
   401     int result = 0;
   402 
   403     if (ctx) {
   404         if (release) {
   405             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   406         }
   407 
   408         jobject inputStream = (jobject)ctx->hidden.androidio.inputStream;
   409 
   410         // inputStream.close();
   411         mEnv->ExceptionClear();
   412         jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   413                 "close", "()V");
   414         mEnv->CallVoidMethod(inputStream, mid);
   415         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   416         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
   417         if (mEnv->ExceptionOccurred()) {
   418             result = -1;
   419             mEnv->ExceptionDescribe();
   420             mEnv->ExceptionClear();
   421         }
   422 
   423         if (release) {
   424             SDL_FreeRW(ctx);
   425         }
   426     }
   427 
   428     return result;
   429 }
   430 
   431 
   432 extern "C" long Android_JNI_FileSeek(SDL_RWops* ctx, long offset, int whence)
   433 {
   434     long newPosition;
   435 
   436     switch (whence) {
   437         case RW_SEEK_SET:
   438             newPosition = offset;
   439             break;
   440         case RW_SEEK_CUR:
   441             newPosition = ctx->hidden.androidio.position + offset;
   442             break;
   443         case RW_SEEK_END:
   444             newPosition = ctx->hidden.androidio.size + offset;
   445             break;
   446         default:
   447             SDL_SetError("Unknown value for 'whence'");
   448             return -1;
   449     }
   450     if (newPosition < 0) {
   451         newPosition = 0;
   452     }
   453     if (newPosition > ctx->hidden.androidio.size) {
   454         newPosition = ctx->hidden.androidio.size;
   455     }
   456 
   457     long movement = newPosition - ctx->hidden.androidio.position;
   458     jobject inputStream = (jobject)ctx->hidden.androidio.inputStream;
   459     jmethodID skipMethod = (jmethodID)ctx->hidden.androidio.skipMethod;
   460 
   461     if (movement > 0) {
   462         // The easy case where we're seeking forwards
   463         mEnv->ExceptionClear();
   464         while (movement > 0) {
   465             // inputStream.skip(...);
   466             movement -= mEnv->CallLongMethod(inputStream, skipMethod, movement);
   467             if (mEnv->ExceptionOccurred()) {
   468                 mEnv->ExceptionDescribe();
   469                 mEnv->ExceptionClear();
   470                 SDL_SetError("Exception while seeking");
   471                 return -1;
   472             }
   473         }
   474     } else if (movement < 0) {
   475         // We can't seek backwards so we have to reopen the file and seek
   476         // forwards which obviously isn't very efficient
   477         Android_JNI_FileClose(ctx, false);
   478         Android_JNI_FileOpen(ctx);
   479         Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
   480     }
   481 
   482     ctx->hidden.androidio.position = newPosition;
   483 
   484     return ctx->hidden.androidio.position;
   485 }
   486 
   487 extern "C" int Android_JNI_FileClose(SDL_RWops* ctx)
   488 {
   489     return Android_JNI_FileClose(ctx, true);
   490 }
   491 
   492 /* vi: set ts=4 sw=4 expandtab: */