src/core/android/SDL_android.cpp
author Ryan C. Gordon <icculus@icculus.org>
Thu, 01 Sep 2011 04:42:09 -0400
changeset 5860 b89f7f3bc9be
parent 5650 640c67302f8e
child 5982 f324bd81b52c
permissions -rw-r--r--
Called method on wrong object in Android exception handler.

Fixes Bugzilla #1297.

Thanks to jon @ rafkind 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 // Test for an exception and call SDL_SetError with its detail if one occurs
   263 static bool Android_JNI_ExceptionOccurred()
   264 {
   265     jthrowable exception = mEnv->ExceptionOccurred();
   266     if (exception != NULL) {
   267         jmethodID mid;
   268 
   269         // Until this happens most JNI operations have undefined behaviour
   270         mEnv->ExceptionClear();
   271 
   272         jclass exceptionClass = mEnv->GetObjectClass(exception);
   273         jclass classClass = mEnv->FindClass("java/lang/Class");
   274 
   275         mid = mEnv->GetMethodID(classClass, "getName", "()Ljava/lang/String;");
   276         jstring exceptionName = (jstring)mEnv->CallObjectMethod(exceptionClass, mid);
   277         const char* exceptionNameUTF8 = mEnv->GetStringUTFChars(exceptionName, 0);
   278 
   279         mid = mEnv->GetMethodID(exceptionClass, "getMessage", "()Ljava/lang/String;");
   280         jstring exceptionMessage = (jstring)mEnv->CallObjectMethod(exception, mid);
   281 
   282         if (exceptionMessage != NULL) {
   283             const char* exceptionMessageUTF8 = mEnv->GetStringUTFChars(
   284                     exceptionMessage, 0);
   285             SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
   286             mEnv->ReleaseStringUTFChars(exceptionMessage, exceptionMessageUTF8);
   287             mEnv->DeleteLocalRef(exceptionMessage);
   288         } else {
   289             SDL_SetError("%s", exceptionNameUTF8);
   290         }
   291 
   292         mEnv->ReleaseStringUTFChars(exceptionName, exceptionNameUTF8);
   293         mEnv->DeleteLocalRef(exceptionName);
   294         mEnv->DeleteLocalRef(classClass);
   295         mEnv->DeleteLocalRef(exceptionClass);
   296         mEnv->DeleteLocalRef(exception);
   297 
   298         return true;
   299     }
   300 
   301     return false;
   302 }
   303 
   304 static int Android_JNI_FileOpen(SDL_RWops* ctx)
   305 {
   306     int result = 0;
   307 
   308     jmethodID mid;
   309     jobject context;
   310     jobject assetManager;
   311     jobject inputStream;
   312     jclass channels;
   313     jobject readableByteChannel;
   314     jstring fileNameJString;
   315 
   316     bool allocatedLocalFrame = false;
   317 
   318     if (mEnv->PushLocalFrame(16) < 0) {
   319         SDL_SetError("Failed to allocate enough JVM local references");
   320         goto failure;
   321     } else {
   322         allocatedLocalFrame = true;
   323     }
   324 
   325     fileNameJString = (jstring)ctx->hidden.androidio.fileName;
   326 
   327     // context = SDLActivity.getContext();
   328     mid = mEnv->GetStaticMethodID(mActivityClass,
   329             "getContext","()Landroid/content/Context;");
   330     context = mEnv->CallStaticObjectMethod(mActivityClass, mid);
   331 
   332     // assetManager = context.getAssets();
   333     mid = mEnv->GetMethodID(mEnv->GetObjectClass(context),
   334             "getAssets", "()Landroid/content/res/AssetManager;");
   335     assetManager = mEnv->CallObjectMethod(context, mid);
   336 
   337     // inputStream = assetManager.open(<filename>);
   338     mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager),
   339             "open", "(Ljava/lang/String;)Ljava/io/InputStream;");
   340     inputStream = mEnv->CallObjectMethod(assetManager, mid, fileNameJString);
   341     if (Android_JNI_ExceptionOccurred()) {
   342         goto failure;
   343     }
   344 
   345     ctx->hidden.androidio.inputStream = inputStream;
   346     ctx->hidden.androidio.inputStreamRef = mEnv->NewGlobalRef(inputStream);
   347 
   348     // Store .skip id for seeking purposes
   349     mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   350             "skip", "(J)J");
   351     ctx->hidden.androidio.skipMethod = mid;
   352 
   353     // Despite all the visible documentation on [Asset]InputStream claiming
   354     // that the .available() method is not guaranteed to return the entire file
   355     // size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
   356     // android/apis/content/ReadAsset.java imply that Android's
   357     // AssetInputStream.available() /will/ always return the total file size
   358 
   359     // size = inputStream.available();
   360     mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   361             "available", "()I");
   362     ctx->hidden.androidio.size = mEnv->CallIntMethod(inputStream, mid);
   363     if (Android_JNI_ExceptionOccurred()) {
   364         goto failure;
   365     }
   366 
   367     // readableByteChannel = Channels.newChannel(inputStream);
   368     channels = mEnv->FindClass("java/nio/channels/Channels");
   369     mid = mEnv->GetStaticMethodID(channels,
   370             "newChannel",
   371             "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
   372     readableByteChannel = mEnv->CallStaticObjectMethod(
   373             channels, mid, inputStream);
   374     if (Android_JNI_ExceptionOccurred()) {
   375         goto failure;
   376     }
   377 
   378     ctx->hidden.androidio.readableByteChannel = readableByteChannel;
   379     ctx->hidden.androidio.readableByteChannelRef =
   380         mEnv->NewGlobalRef(readableByteChannel);
   381 
   382     // Store .read id for reading purposes
   383     mid = mEnv->GetMethodID(mEnv->GetObjectClass(readableByteChannel),
   384             "read", "(Ljava/nio/ByteBuffer;)I");
   385     ctx->hidden.androidio.readMethod = mid;
   386 
   387     ctx->hidden.androidio.position = 0;
   388 
   389     if (false) {
   390 failure:
   391         result = -1;
   392 
   393         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   394 
   395         if(ctx->hidden.androidio.inputStreamRef != NULL) {
   396             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   397         }
   398     }
   399 
   400     if (allocatedLocalFrame) {
   401         mEnv->PopLocalFrame(NULL);
   402     }
   403 
   404     return result;
   405 }
   406 
   407 extern "C" int Android_JNI_FileOpen(SDL_RWops* ctx,
   408         const char* fileName, const char*)
   409 {
   410     if (!ctx) {
   411         return -1;
   412     }
   413 
   414     jstring fileNameJString = mEnv->NewStringUTF(fileName);
   415     ctx->hidden.androidio.fileName = fileNameJString;
   416     ctx->hidden.androidio.fileNameRef = mEnv->NewGlobalRef(fileNameJString);
   417     ctx->hidden.androidio.inputStreamRef = NULL;
   418     mEnv->DeleteLocalRef(fileNameJString);
   419 
   420     return Android_JNI_FileOpen(ctx);
   421 }
   422 
   423 extern "C" size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
   424         size_t size, size_t maxnum)
   425 {
   426     int bytesRemaining = size * maxnum;
   427     int bytesRead = 0;
   428 
   429     jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannel;
   430     jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
   431     jobject byteBuffer = mEnv->NewDirectByteBuffer(buffer, bytesRemaining);
   432 
   433     while (bytesRemaining > 0) {
   434         // result = readableByteChannel.read(...);
   435         int result = mEnv->CallIntMethod(readableByteChannel, readMethod, byteBuffer);
   436 
   437         if (Android_JNI_ExceptionOccurred()) {
   438             mEnv->DeleteLocalRef(byteBuffer);
   439             return 0;
   440         }
   441 
   442         if (result < 0) {
   443             break;
   444         }
   445 
   446         bytesRemaining -= result;
   447         bytesRead += result;
   448         ctx->hidden.androidio.position += result;
   449     }
   450 
   451     mEnv->DeleteLocalRef(byteBuffer);
   452 
   453     return bytesRead / size;
   454 }
   455 
   456 extern "C" size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
   457         size_t size, size_t num)
   458 {
   459     SDL_SetError("Cannot write to Android package filesystem");
   460     return 0;
   461 }
   462 
   463 static int Android_JNI_FileClose(SDL_RWops* ctx, bool release)
   464 {
   465     int result = 0;
   466 
   467     if (ctx) {
   468         if (release) {
   469             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   470         }
   471 
   472         jobject inputStream = (jobject)ctx->hidden.androidio.inputStream;
   473 
   474         // inputStream.close();
   475         jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   476                 "close", "()V");
   477         mEnv->CallVoidMethod(inputStream, mid);
   478         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   479         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
   480         if (Android_JNI_ExceptionOccurred()) {
   481             result = -1;
   482         }
   483 
   484         if (release) {
   485             SDL_FreeRW(ctx);
   486         }
   487     }
   488 
   489     return result;
   490 }
   491 
   492 
   493 extern "C" long Android_JNI_FileSeek(SDL_RWops* ctx, long offset, int whence)
   494 {
   495     long newPosition;
   496 
   497     switch (whence) {
   498         case RW_SEEK_SET:
   499             newPosition = offset;
   500             break;
   501         case RW_SEEK_CUR:
   502             newPosition = ctx->hidden.androidio.position + offset;
   503             break;
   504         case RW_SEEK_END:
   505             newPosition = ctx->hidden.androidio.size + offset;
   506             break;
   507         default:
   508             SDL_SetError("Unknown value for 'whence'");
   509             return -1;
   510     }
   511     if (newPosition < 0) {
   512         newPosition = 0;
   513     }
   514     if (newPosition > ctx->hidden.androidio.size) {
   515         newPosition = ctx->hidden.androidio.size;
   516     }
   517 
   518     long movement = newPosition - ctx->hidden.androidio.position;
   519     jobject inputStream = (jobject)ctx->hidden.androidio.inputStream;
   520     jmethodID skipMethod = (jmethodID)ctx->hidden.androidio.skipMethod;
   521 
   522     if (movement > 0) {
   523         // The easy case where we're seeking forwards
   524         while (movement > 0) {
   525             // inputStream.skip(...);
   526             movement -= mEnv->CallLongMethod(inputStream, skipMethod, movement);
   527             if (Android_JNI_ExceptionOccurred()) {
   528                 return -1;
   529             }
   530         }
   531     } else if (movement < 0) {
   532         // We can't seek backwards so we have to reopen the file and seek
   533         // forwards which obviously isn't very efficient
   534         Android_JNI_FileClose(ctx, false);
   535         Android_JNI_FileOpen(ctx);
   536         Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
   537     }
   538 
   539     ctx->hidden.androidio.position = newPosition;
   540 
   541     return ctx->hidden.androidio.position;
   542 }
   543 
   544 extern "C" int Android_JNI_FileClose(SDL_RWops* ctx)
   545 {
   546     return Android_JNI_FileClose(ctx, true);
   547 }
   548 
   549 /* vi: set ts=4 sw=4 expandtab: */