Fixes issue #1500 "SDL_RWops fails under Android 4" by removing stale Local Refs
and replacing them for their global equivalents.
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2012 Sam Lantinga <slouken@libsdl.org>
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.
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:
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.
21 #include "SDL_config.h"
22 #include "SDL_stdinc.h"
23 #include "SDL_assert.h"
27 #include "SDL_android.h"
30 #include "../../events/SDL_events_c.h"
31 #include "../../video/android/SDL_androidkeyboard.h"
32 #include "../../video/android/SDL_androidtouch.h"
33 #include "../../video/android/SDL_androidvideo.h"
35 #include <android/log.h>
36 #define LOG_TAG "SDL_android"
37 //#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
38 //#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
39 #define LOGI(...) do {} while (false)
40 #define LOGE(...) do {} while (false)
43 /* Implemented in audio/android/SDL_androidaudio.c */
44 extern void Android_RunAudioThread();
47 /*******************************************************************************
48 This file links the Java side of Android with libsdl
49 *******************************************************************************/
51 #include <android/log.h>
54 /*******************************************************************************
56 *******************************************************************************/
57 static JNIEnv* mEnv = NULL;
58 static JNIEnv* mAudioEnv = NULL;
59 static JavaVM* mJavaVM;
62 static jclass mActivityClass;
65 static jmethodID midCreateGLContext;
66 static jmethodID midFlipBuffers;
67 static jmethodID midAudioInit;
68 static jmethodID midAudioWriteShortBuffer;
69 static jmethodID midAudioWriteByteBuffer;
70 static jmethodID midAudioQuit;
72 // Accelerometer data storage
73 static float fLastAccelerometer[3];
74 static bool bHasNewData;
76 /*******************************************************************************
77 Functions called by JNI
78 *******************************************************************************/
81 extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
85 LOGI("JNI_OnLoad called");
86 if (mJavaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
87 LOGE("Failed to get the environment using GetEnv()");
91 return JNI_VERSION_1_4;
94 // Called before SDL_main() to initialize JNI bindings
95 extern "C" void SDL_Android_Init(JNIEnv* env, jclass cls)
97 __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init()");
100 mActivityClass = (jclass)env->NewGlobalRef(cls);
102 midCreateGLContext = mEnv->GetStaticMethodID(mActivityClass,
103 "createGLContext","(II)Z");
104 midFlipBuffers = mEnv->GetStaticMethodID(mActivityClass,
105 "flipBuffers","()V");
106 midAudioInit = mEnv->GetStaticMethodID(mActivityClass,
107 "audioInit", "(IZZI)Ljava/lang/Object;");
108 midAudioWriteShortBuffer = mEnv->GetStaticMethodID(mActivityClass,
109 "audioWriteShortBuffer", "([S)V");
110 midAudioWriteByteBuffer = mEnv->GetStaticMethodID(mActivityClass,
111 "audioWriteByteBuffer", "([B)V");
112 midAudioQuit = mEnv->GetStaticMethodID(mActivityClass,
117 if(!midCreateGLContext || !midFlipBuffers || !midAudioInit ||
118 !midAudioWriteShortBuffer || !midAudioWriteByteBuffer || !midAudioQuit) {
119 __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL: Couldn't locate Java callbacks, check that they're named and typed correctly");
121 __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init() finished!");
125 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeResize(
126 JNIEnv* env, jclass jcls,
127 jint width, jint height, jint format)
129 Android_SetScreenResolution(width, height, format);
133 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyDown(
134 JNIEnv* env, jclass jcls, jint keycode)
136 Android_OnKeyDown(keycode);
140 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyUp(
141 JNIEnv* env, jclass jcls, jint keycode)
143 Android_OnKeyUp(keycode);
147 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeTouch(
148 JNIEnv* env, jclass jcls,
149 jint touch_device_id_in, jint pointer_finger_id_in,
150 jint action, jfloat x, jfloat y, jfloat p)
152 Android_OnTouch(touch_device_id_in, pointer_finger_id_in, action, x, y, p);
156 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeAccel(
157 JNIEnv* env, jclass jcls,
158 jfloat x, jfloat y, jfloat z)
160 fLastAccelerometer[0] = x;
161 fLastAccelerometer[1] = y;
162 fLastAccelerometer[2] = z;
167 extern "C" void Java_org_libsdl_app_SDLActivity_nativeQuit(
168 JNIEnv* env, jclass cls)
170 // Inject a SDL_QUIT event
175 extern "C" void Java_org_libsdl_app_SDLActivity_nativePause(
176 JNIEnv* env, jclass cls)
178 if (Android_Window) {
179 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_LOST, 0, 0);
180 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_MINIMIZED, 0, 0);
185 extern "C" void Java_org_libsdl_app_SDLActivity_nativeResume(
186 JNIEnv* env, jclass cls)
188 if (Android_Window) {
189 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_GAINED, 0, 0);
190 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_RESTORED, 0, 0);
194 extern "C" void Java_org_libsdl_app_SDLActivity_nativeRunAudioThread(
195 JNIEnv* env, jclass cls)
197 /* This is the audio thread, with a different environment */
200 Android_RunAudioThread();
204 /*******************************************************************************
205 Functions called by SDL into Java
206 *******************************************************************************/
208 class LocalReferenceHolder
214 static bool IsActive() {
219 LocalReferenceHolder() : m_env(NULL) { }
220 ~LocalReferenceHolder() {
222 m_env->PopLocalFrame(NULL);
227 bool init(JNIEnv *env, jint capacity = 16) {
228 if (env->PushLocalFrame(capacity) < 0) {
229 SDL_SetError("Failed to allocate enough JVM local references");
240 int LocalReferenceHolder::s_active;
242 extern "C" SDL_bool Android_JNI_CreateContext(int majorVersion, int minorVersion)
244 if (mEnv->CallStaticBooleanMethod(mActivityClass, midCreateGLContext, majorVersion, minorVersion)) {
251 extern "C" void Android_JNI_SwapWindow()
253 mEnv->CallStaticVoidMethod(mActivityClass, midFlipBuffers);
256 extern "C" void Android_JNI_SetActivityTitle(const char *title)
260 mid = mEnv->GetStaticMethodID(mActivityClass,"setActivityTitle","(Ljava/lang/String;)V");
262 jstring jtitle = reinterpret_cast<jstring>(mEnv->NewStringUTF(title));
263 mEnv->CallStaticVoidMethod(mActivityClass, mid, jtitle);
264 mEnv->DeleteLocalRef(jtitle);
268 extern "C" SDL_bool Android_JNI_GetAccelerometerValues(float values[3])
271 SDL_bool retval = SDL_FALSE;
274 for (i = 0; i < 3; ++i) {
275 values[i] = fLastAccelerometer[i];
287 static jboolean audioBuffer16Bit = JNI_FALSE;
288 static jboolean audioBufferStereo = JNI_FALSE;
289 static jobject audioBuffer = NULL;
290 static void* audioBufferPinned = NULL;
292 extern "C" int Android_JNI_OpenAudioDevice(int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
294 int audioBufferFrames;
298 static bool isAttached = false;
299 status = mJavaVM->GetEnv((void **) &env, JNI_VERSION_1_4);
301 LOGE("callback_handler: failed to get JNI environment, assuming native thread");
302 status = mJavaVM->AttachCurrentThread(&env, NULL);
304 LOGE("callback_handler: failed to attach current thread");
311 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device");
312 audioBuffer16Bit = is16Bit;
313 audioBufferStereo = channelCount > 1;
315 audioBuffer = env->CallStaticObjectMethod(mActivityClass, midAudioInit, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames);
317 if (audioBuffer == NULL) {
318 __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: didn't get back a good audio buffer!");
321 audioBuffer = env->NewGlobalRef(audioBuffer);
323 jboolean isCopy = JNI_FALSE;
324 if (audioBuffer16Bit) {
325 audioBufferPinned = env->GetShortArrayElements((jshortArray)audioBuffer, &isCopy);
326 audioBufferFrames = env->GetArrayLength((jshortArray)audioBuffer);
328 audioBufferPinned = env->GetByteArrayElements((jbyteArray)audioBuffer, &isCopy);
329 audioBufferFrames = env->GetArrayLength((jbyteArray)audioBuffer);
331 if (audioBufferStereo) {
332 audioBufferFrames /= 2;
336 mJavaVM->DetachCurrentThread();
339 return audioBufferFrames;
342 extern "C" void * Android_JNI_GetAudioBuffer()
344 return audioBufferPinned;
347 extern "C" void Android_JNI_WriteAudioBuffer()
349 if (audioBuffer16Bit) {
350 mAudioEnv->ReleaseShortArrayElements((jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
351 mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
353 mAudioEnv->ReleaseByteArrayElements((jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT);
354 mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer);
357 /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
360 extern "C" void Android_JNI_CloseAudioDevice()
364 static bool isAttached = false;
365 status = mJavaVM->GetEnv((void **) &env, JNI_VERSION_1_4);
367 LOGE("callback_handler: failed to get JNI environment, assuming native thread");
368 status = mJavaVM->AttachCurrentThread(&env, NULL);
370 LOGE("callback_handler: failed to attach current thread");
376 env->CallStaticVoidMethod(mActivityClass, midAudioQuit);
379 env->DeleteGlobalRef(audioBuffer);
381 audioBufferPinned = NULL;
385 mJavaVM->DetachCurrentThread();
389 // Test for an exception and call SDL_SetError with its detail if one occurs
390 static bool Android_JNI_ExceptionOccurred()
392 SDL_assert(LocalReferenceHolder::IsActive());
394 jthrowable exception = mEnv->ExceptionOccurred();
395 if (exception != NULL) {
398 // Until this happens most JNI operations have undefined behaviour
399 mEnv->ExceptionClear();
401 jclass exceptionClass = mEnv->GetObjectClass(exception);
402 jclass classClass = mEnv->FindClass("java/lang/Class");
404 mid = mEnv->GetMethodID(classClass, "getName", "()Ljava/lang/String;");
405 jstring exceptionName = (jstring)mEnv->CallObjectMethod(exceptionClass, mid);
406 const char* exceptionNameUTF8 = mEnv->GetStringUTFChars(exceptionName, 0);
408 mid = mEnv->GetMethodID(exceptionClass, "getMessage", "()Ljava/lang/String;");
409 jstring exceptionMessage = (jstring)mEnv->CallObjectMethod(exception, mid);
411 if (exceptionMessage != NULL) {
412 const char* exceptionMessageUTF8 = mEnv->GetStringUTFChars(
413 exceptionMessage, 0);
414 SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
415 mEnv->ReleaseStringUTFChars(exceptionMessage, exceptionMessageUTF8);
417 SDL_SetError("%s", exceptionNameUTF8);
420 mEnv->ReleaseStringUTFChars(exceptionName, exceptionNameUTF8);
428 static int Android_JNI_FileOpen(SDL_RWops* ctx)
430 LocalReferenceHolder refs;
435 jobject assetManager;
438 jobject readableByteChannel;
439 jstring fileNameJString;
441 if (!refs.init(mEnv)) {
445 fileNameJString = (jstring)ctx->hidden.androidio.fileNameRef;
447 // context = SDLActivity.getContext();
448 mid = mEnv->GetStaticMethodID(mActivityClass,
449 "getContext","()Landroid/content/Context;");
450 context = mEnv->CallStaticObjectMethod(mActivityClass, mid);
452 // assetManager = context.getAssets();
453 mid = mEnv->GetMethodID(mEnv->GetObjectClass(context),
454 "getAssets", "()Landroid/content/res/AssetManager;");
455 assetManager = mEnv->CallObjectMethod(context, mid);
457 // inputStream = assetManager.open(<filename>);
458 mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager),
459 "open", "(Ljava/lang/String;)Ljava/io/InputStream;");
460 inputStream = mEnv->CallObjectMethod(assetManager, mid, fileNameJString);
461 if (Android_JNI_ExceptionOccurred()) {
465 ctx->hidden.androidio.inputStreamRef = mEnv->NewGlobalRef(inputStream);
467 // Despite all the visible documentation on [Asset]InputStream claiming
468 // that the .available() method is not guaranteed to return the entire file
469 // size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
470 // android/apis/content/ReadAsset.java imply that Android's
471 // AssetInputStream.available() /will/ always return the total file size
473 // size = inputStream.available();
474 mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
476 ctx->hidden.androidio.size = mEnv->CallIntMethod(inputStream, mid);
477 if (Android_JNI_ExceptionOccurred()) {
481 // readableByteChannel = Channels.newChannel(inputStream);
482 channels = mEnv->FindClass("java/nio/channels/Channels");
483 mid = mEnv->GetStaticMethodID(channels,
485 "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
486 readableByteChannel = mEnv->CallStaticObjectMethod(
487 channels, mid, inputStream);
488 if (Android_JNI_ExceptionOccurred()) {
492 ctx->hidden.androidio.readableByteChannelRef =
493 mEnv->NewGlobalRef(readableByteChannel);
495 // Store .read id for reading purposes
496 mid = mEnv->GetMethodID(mEnv->GetObjectClass(readableByteChannel),
497 "read", "(Ljava/nio/ByteBuffer;)I");
498 ctx->hidden.androidio.readMethod = mid;
500 ctx->hidden.androidio.position = 0;
506 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
508 if(ctx->hidden.androidio.inputStreamRef != NULL) {
509 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
512 if(ctx->hidden.androidio.readableByteChannelRef != NULL) {
513 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
521 extern "C" int Android_JNI_FileOpen(SDL_RWops* ctx,
522 const char* fileName, const char*)
524 LocalReferenceHolder refs;
526 if (!refs.init(mEnv)) {
534 jstring fileNameJString = mEnv->NewStringUTF(fileName);
535 ctx->hidden.androidio.fileNameRef = mEnv->NewGlobalRef(fileNameJString);
536 ctx->hidden.androidio.inputStreamRef = NULL;
538 return Android_JNI_FileOpen(ctx);
541 extern "C" size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
542 size_t size, size_t maxnum)
544 LocalReferenceHolder refs;
545 int bytesRemaining = size * maxnum;
548 if (!refs.init(mEnv)) {
552 jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannelRef;
553 jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
554 jobject byteBuffer = mEnv->NewDirectByteBuffer(buffer, bytesRemaining);
556 while (bytesRemaining > 0) {
557 // result = readableByteChannel.read(...);
558 int result = mEnv->CallIntMethod(readableByteChannel, readMethod, byteBuffer);
560 if (Android_JNI_ExceptionOccurred()) {
568 bytesRemaining -= result;
570 ctx->hidden.androidio.position += result;
573 return bytesRead / size;
576 extern "C" size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
577 size_t size, size_t num)
579 SDL_SetError("Cannot write to Android package filesystem");
583 static int Android_JNI_FileClose(SDL_RWops* ctx, bool release)
585 LocalReferenceHolder refs;
588 if (!refs.init(mEnv)) {
589 SDL_SetError("Failed to allocate enough JVM local references");
595 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
598 jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
600 // inputStream.close();
601 jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
603 mEnv->CallVoidMethod(inputStream, mid);
604 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
605 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
606 if (Android_JNI_ExceptionOccurred()) {
619 extern "C" long Android_JNI_FileSeek(SDL_RWops* ctx, long offset, int whence)
625 newPosition = offset;
628 newPosition = ctx->hidden.androidio.position + offset;
631 newPosition = ctx->hidden.androidio.size + offset;
634 SDL_SetError("Unknown value for 'whence'");
637 if (newPosition < 0) {
640 if (newPosition > ctx->hidden.androidio.size) {
641 newPosition = ctx->hidden.androidio.size;
644 long movement = newPosition - ctx->hidden.androidio.position;
645 jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
648 unsigned char buffer[1024];
650 // The easy case where we're seeking forwards
651 while (movement > 0) {
652 long amount = (long) sizeof (buffer);
653 if (amount > movement) {
656 size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount);
659 // Failed to read/skip the required amount, so fail
665 } else if (movement < 0) {
666 // We can't seek backwards so we have to reopen the file and seek
667 // forwards which obviously isn't very efficient
668 Android_JNI_FileClose(ctx, false);
669 Android_JNI_FileOpen(ctx);
670 Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
673 ctx->hidden.androidio.position = newPosition;
675 return ctx->hidden.androidio.position;
678 extern "C" int Android_JNI_FileClose(SDL_RWops* ctx)
680 return Android_JNI_FileClose(ctx, true);
683 #endif /* __ANDROID__ */
685 /* vi: set ts=4 sw=4 expandtab: */