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