src/core/android/SDL_android.cpp
author Ryan C. Gordon <icculus@icculus.org>
Thu, 13 Oct 2011 01:21:35 -0400
changeset 5982 f324bd81b52c
parent 5860 b89f7f3bc9be
child 5993 c9cb52d6d864
permissions -rw-r--r--
Added support for multitouch on Android.

Fixes Bugzilla #1294.

Thanks to Gabriel Jacobo 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 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     // Store .skip id for seeking purposes
   350     mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   351             "skip", "(J)J");
   352     ctx->hidden.androidio.skipMethod = mid;
   353 
   354     // Despite all the visible documentation on [Asset]InputStream claiming
   355     // that the .available() method is not guaranteed to return the entire file
   356     // size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
   357     // android/apis/content/ReadAsset.java imply that Android's
   358     // AssetInputStream.available() /will/ always return the total file size
   359 
   360     // size = inputStream.available();
   361     mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   362             "available", "()I");
   363     ctx->hidden.androidio.size = mEnv->CallIntMethod(inputStream, mid);
   364     if (Android_JNI_ExceptionOccurred()) {
   365         goto failure;
   366     }
   367 
   368     // readableByteChannel = Channels.newChannel(inputStream);
   369     channels = mEnv->FindClass("java/nio/channels/Channels");
   370     mid = mEnv->GetStaticMethodID(channels,
   371             "newChannel",
   372             "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
   373     readableByteChannel = mEnv->CallStaticObjectMethod(
   374             channels, mid, inputStream);
   375     if (Android_JNI_ExceptionOccurred()) {
   376         goto failure;
   377     }
   378 
   379     ctx->hidden.androidio.readableByteChannel = readableByteChannel;
   380     ctx->hidden.androidio.readableByteChannelRef =
   381         mEnv->NewGlobalRef(readableByteChannel);
   382 
   383     // Store .read id for reading purposes
   384     mid = mEnv->GetMethodID(mEnv->GetObjectClass(readableByteChannel),
   385             "read", "(Ljava/nio/ByteBuffer;)I");
   386     ctx->hidden.androidio.readMethod = mid;
   387 
   388     ctx->hidden.androidio.position = 0;
   389 
   390     if (false) {
   391 failure:
   392         result = -1;
   393 
   394         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   395 
   396         if(ctx->hidden.androidio.inputStreamRef != NULL) {
   397             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   398         }
   399     }
   400 
   401     if (allocatedLocalFrame) {
   402         mEnv->PopLocalFrame(NULL);
   403     }
   404 
   405     return result;
   406 }
   407 
   408 extern "C" int Android_JNI_FileOpen(SDL_RWops* ctx,
   409         const char* fileName, const char*)
   410 {
   411     if (!ctx) {
   412         return -1;
   413     }
   414 
   415     jstring fileNameJString = mEnv->NewStringUTF(fileName);
   416     ctx->hidden.androidio.fileName = fileNameJString;
   417     ctx->hidden.androidio.fileNameRef = mEnv->NewGlobalRef(fileNameJString);
   418     ctx->hidden.androidio.inputStreamRef = NULL;
   419     mEnv->DeleteLocalRef(fileNameJString);
   420 
   421     return Android_JNI_FileOpen(ctx);
   422 }
   423 
   424 extern "C" size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
   425         size_t size, size_t maxnum)
   426 {
   427     int bytesRemaining = size * maxnum;
   428     int bytesRead = 0;
   429 
   430     jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannel;
   431     jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
   432     jobject byteBuffer = mEnv->NewDirectByteBuffer(buffer, bytesRemaining);
   433 
   434     while (bytesRemaining > 0) {
   435         // result = readableByteChannel.read(...);
   436         int result = mEnv->CallIntMethod(readableByteChannel, readMethod, byteBuffer);
   437 
   438         if (Android_JNI_ExceptionOccurred()) {
   439             mEnv->DeleteLocalRef(byteBuffer);
   440             return 0;
   441         }
   442 
   443         if (result < 0) {
   444             break;
   445         }
   446 
   447         bytesRemaining -= result;
   448         bytesRead += result;
   449         ctx->hidden.androidio.position += result;
   450     }
   451 
   452     mEnv->DeleteLocalRef(byteBuffer);
   453 
   454     return bytesRead / size;
   455 }
   456 
   457 extern "C" size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
   458         size_t size, size_t num)
   459 {
   460     SDL_SetError("Cannot write to Android package filesystem");
   461     return 0;
   462 }
   463 
   464 static int Android_JNI_FileClose(SDL_RWops* ctx, bool release)
   465 {
   466     int result = 0;
   467 
   468     if (ctx) {
   469         if (release) {
   470             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   471         }
   472 
   473         jobject inputStream = (jobject)ctx->hidden.androidio.inputStream;
   474 
   475         // inputStream.close();
   476         jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   477                 "close", "()V");
   478         mEnv->CallVoidMethod(inputStream, mid);
   479         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   480         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
   481         if (Android_JNI_ExceptionOccurred()) {
   482             result = -1;
   483         }
   484 
   485         if (release) {
   486             SDL_FreeRW(ctx);
   487         }
   488     }
   489 
   490     return result;
   491 }
   492 
   493 
   494 extern "C" long Android_JNI_FileSeek(SDL_RWops* ctx, long offset, int whence)
   495 {
   496     long newPosition;
   497 
   498     switch (whence) {
   499         case RW_SEEK_SET:
   500             newPosition = offset;
   501             break;
   502         case RW_SEEK_CUR:
   503             newPosition = ctx->hidden.androidio.position + offset;
   504             break;
   505         case RW_SEEK_END:
   506             newPosition = ctx->hidden.androidio.size + offset;
   507             break;
   508         default:
   509             SDL_SetError("Unknown value for 'whence'");
   510             return -1;
   511     }
   512     if (newPosition < 0) {
   513         newPosition = 0;
   514     }
   515     if (newPosition > ctx->hidden.androidio.size) {
   516         newPosition = ctx->hidden.androidio.size;
   517     }
   518 
   519     long movement = newPosition - ctx->hidden.androidio.position;
   520     jobject inputStream = (jobject)ctx->hidden.androidio.inputStream;
   521     jmethodID skipMethod = (jmethodID)ctx->hidden.androidio.skipMethod;
   522 
   523     if (movement > 0) {
   524         // The easy case where we're seeking forwards
   525         while (movement > 0) {
   526             // inputStream.skip(...);
   527             movement -= mEnv->CallLongMethod(inputStream, skipMethod, movement);
   528             if (Android_JNI_ExceptionOccurred()) {
   529                 return -1;
   530             }
   531         }
   532     } else if (movement < 0) {
   533         // We can't seek backwards so we have to reopen the file and seek
   534         // forwards which obviously isn't very efficient
   535         Android_JNI_FileClose(ctx, false);
   536         Android_JNI_FileOpen(ctx);
   537         Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
   538     }
   539 
   540     ctx->hidden.androidio.position = newPosition;
   541 
   542     return ctx->hidden.androidio.position;
   543 }
   544 
   545 extern "C" int Android_JNI_FileClose(SDL_RWops* ctx)
   546 {
   547     return Android_JNI_FileClose(ctx, true);
   548 }
   549 
   550 /* vi: set ts=4 sw=4 expandtab: */