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.fileName;
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.inputStream = inputStream;
466 ctx->hidden.androidio.inputStreamRef = mEnv->NewGlobalRef(inputStream);
468 // Despite all the visible documentation on [Asset]InputStream claiming
469 // that the .available() method is not guaranteed to return the entire file
470 // size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
471 // android/apis/content/ReadAsset.java imply that Android's
472 // AssetInputStream.available() /will/ always return the total file size
474 // size = inputStream.available();
475 mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
477 ctx->hidden.androidio.size = mEnv->CallIntMethod(inputStream, mid);
478 if (Android_JNI_ExceptionOccurred()) {
482 // readableByteChannel = Channels.newChannel(inputStream);
483 channels = mEnv->FindClass("java/nio/channels/Channels");
484 mid = mEnv->GetStaticMethodID(channels,
486 "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
487 readableByteChannel = mEnv->CallStaticObjectMethod(
488 channels, mid, inputStream);
489 if (Android_JNI_ExceptionOccurred()) {
493 ctx->hidden.androidio.readableByteChannel = readableByteChannel;
494 ctx->hidden.androidio.readableByteChannelRef =
495 mEnv->NewGlobalRef(readableByteChannel);
497 // Store .read id for reading purposes
498 mid = mEnv->GetMethodID(mEnv->GetObjectClass(readableByteChannel),
499 "read", "(Ljava/nio/ByteBuffer;)I");
500 ctx->hidden.androidio.readMethod = mid;
502 ctx->hidden.androidio.position = 0;
508 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
510 if(ctx->hidden.androidio.inputStreamRef != NULL) {
511 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
518 extern "C" int Android_JNI_FileOpen(SDL_RWops* ctx,
519 const char* fileName, const char*)
521 LocalReferenceHolder refs;
523 if (!refs.init(mEnv)) {
531 jstring fileNameJString = mEnv->NewStringUTF(fileName);
532 ctx->hidden.androidio.fileName = fileNameJString;
533 ctx->hidden.androidio.fileNameRef = mEnv->NewGlobalRef(fileNameJString);
534 ctx->hidden.androidio.inputStreamRef = NULL;
536 return Android_JNI_FileOpen(ctx);
539 extern "C" size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
540 size_t size, size_t maxnum)
542 LocalReferenceHolder refs;
543 int bytesRemaining = size * maxnum;
546 if (!refs.init(mEnv)) {
550 jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannel;
551 jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
552 jobject byteBuffer = mEnv->NewDirectByteBuffer(buffer, bytesRemaining);
554 while (bytesRemaining > 0) {
555 // result = readableByteChannel.read(...);
556 int result = mEnv->CallIntMethod(readableByteChannel, readMethod, byteBuffer);
558 if (Android_JNI_ExceptionOccurred()) {
566 bytesRemaining -= result;
568 ctx->hidden.androidio.position += result;
571 return bytesRead / size;
574 extern "C" size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
575 size_t size, size_t num)
577 SDL_SetError("Cannot write to Android package filesystem");
581 static int Android_JNI_FileClose(SDL_RWops* ctx, bool release)
583 LocalReferenceHolder refs;
586 if (!refs.init(mEnv)) {
587 SDL_SetError("Failed to allocate enough JVM local references");
593 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
596 jobject inputStream = (jobject)ctx->hidden.androidio.inputStream;
598 // inputStream.close();
599 jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
601 mEnv->CallVoidMethod(inputStream, mid);
602 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
603 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
604 if (Android_JNI_ExceptionOccurred()) {
617 extern "C" long Android_JNI_FileSeek(SDL_RWops* ctx, long offset, int whence)
623 newPosition = offset;
626 newPosition = ctx->hidden.androidio.position + offset;
629 newPosition = ctx->hidden.androidio.size + offset;
632 SDL_SetError("Unknown value for 'whence'");
635 if (newPosition < 0) {
638 if (newPosition > ctx->hidden.androidio.size) {
639 newPosition = ctx->hidden.androidio.size;
642 long movement = newPosition - ctx->hidden.androidio.position;
643 jobject inputStream = (jobject)ctx->hidden.androidio.inputStream;
646 unsigned char buffer[1024];
648 // The easy case where we're seeking forwards
649 while (movement > 0) {
650 long amount = (long) sizeof (buffer);
651 if (amount > movement) {
654 size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount);
657 // Failed to read/skip the required amount, so fail
663 } else if (movement < 0) {
664 // We can't seek backwards so we have to reopen the file and seek
665 // forwards which obviously isn't very efficient
666 Android_JNI_FileClose(ctx, false);
667 Android_JNI_FileOpen(ctx);
668 Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
671 ctx->hidden.androidio.position = newPosition;
673 return ctx->hidden.androidio.position;
676 extern "C" int Android_JNI_FileClose(SDL_RWops* ctx)
678 return Android_JNI_FileClose(ctx, true);
681 #endif /* __ANDROID__ */
683 /* vi: set ts=4 sw=4 expandtab: */