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 mEnv->CallStaticVoidMethod(mActivityClass, mid, mEnv->NewStringUTF(title));
266 extern "C" SDL_bool Android_JNI_GetAccelerometerValues(float values[3])
269 SDL_bool retval = SDL_FALSE;
272 for (i = 0; i < 3; ++i) {
273 values[i] = fLastAccelerometer[i];
285 static jboolean audioBuffer16Bit = JNI_FALSE;
286 static jboolean audioBufferStereo = JNI_FALSE;
287 static jobject audioBuffer = NULL;
288 static void* audioBufferPinned = NULL;
290 extern "C" int Android_JNI_OpenAudioDevice(int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
292 int audioBufferFrames;
296 static bool isAttached = false;
297 status = mJavaVM->GetEnv((void **) &env, JNI_VERSION_1_4);
299 LOGE("callback_handler: failed to get JNI environment, assuming native thread");
300 status = mJavaVM->AttachCurrentThread(&env, NULL);
302 LOGE("callback_handler: failed to attach current thread");
309 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device");
310 audioBuffer16Bit = is16Bit;
311 audioBufferStereo = channelCount > 1;
313 audioBuffer = env->CallStaticObjectMethod(mActivityClass, midAudioInit, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames);
315 if (audioBuffer == NULL) {
316 __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: didn't get back a good audio buffer!");
319 audioBuffer = env->NewGlobalRef(audioBuffer);
321 jboolean isCopy = JNI_FALSE;
322 if (audioBuffer16Bit) {
323 audioBufferPinned = env->GetShortArrayElements((jshortArray)audioBuffer, &isCopy);
324 audioBufferFrames = env->GetArrayLength((jshortArray)audioBuffer);
326 audioBufferPinned = env->GetByteArrayElements((jbyteArray)audioBuffer, &isCopy);
327 audioBufferFrames = env->GetArrayLength((jbyteArray)audioBuffer);
329 if (audioBufferStereo) {
330 audioBufferFrames /= 2;
334 mJavaVM->DetachCurrentThread();
337 return audioBufferFrames;
340 extern "C" void * Android_JNI_GetAudioBuffer()
342 return audioBufferPinned;
345 extern "C" void Android_JNI_WriteAudioBuffer()
347 if (audioBuffer16Bit) {
348 mAudioEnv->ReleaseShortArrayElements((jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
349 mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
351 mAudioEnv->ReleaseByteArrayElements((jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT);
352 mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer);
355 /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
358 extern "C" void Android_JNI_CloseAudioDevice()
362 static bool isAttached = false;
363 status = mJavaVM->GetEnv((void **) &env, JNI_VERSION_1_4);
365 LOGE("callback_handler: failed to get JNI environment, assuming native thread");
366 status = mJavaVM->AttachCurrentThread(&env, NULL);
368 LOGE("callback_handler: failed to attach current thread");
374 env->CallStaticVoidMethod(mActivityClass, midAudioQuit);
377 env->DeleteGlobalRef(audioBuffer);
379 audioBufferPinned = NULL;
383 mJavaVM->DetachCurrentThread();
387 // Test for an exception and call SDL_SetError with its detail if one occurs
388 static bool Android_JNI_ExceptionOccurred()
390 SDL_assert(LocalReferenceHolder::IsActive());
392 jthrowable exception = mEnv->ExceptionOccurred();
393 if (exception != NULL) {
396 // Until this happens most JNI operations have undefined behaviour
397 mEnv->ExceptionClear();
399 jclass exceptionClass = mEnv->GetObjectClass(exception);
400 jclass classClass = mEnv->FindClass("java/lang/Class");
402 mid = mEnv->GetMethodID(classClass, "getName", "()Ljava/lang/String;");
403 jstring exceptionName = (jstring)mEnv->CallObjectMethod(exceptionClass, mid);
404 const char* exceptionNameUTF8 = mEnv->GetStringUTFChars(exceptionName, 0);
406 mid = mEnv->GetMethodID(exceptionClass, "getMessage", "()Ljava/lang/String;");
407 jstring exceptionMessage = (jstring)mEnv->CallObjectMethod(exception, mid);
409 if (exceptionMessage != NULL) {
410 const char* exceptionMessageUTF8 = mEnv->GetStringUTFChars(
411 exceptionMessage, 0);
412 SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
413 mEnv->ReleaseStringUTFChars(exceptionMessage, exceptionMessageUTF8);
415 SDL_SetError("%s", exceptionNameUTF8);
418 mEnv->ReleaseStringUTFChars(exceptionName, exceptionNameUTF8);
426 static int Android_JNI_FileOpen(SDL_RWops* ctx)
428 LocalReferenceHolder refs;
433 jobject assetManager;
436 jobject readableByteChannel;
437 jstring fileNameJString;
439 if (!refs.init(mEnv)) {
443 fileNameJString = (jstring)ctx->hidden.androidio.fileName;
445 // context = SDLActivity.getContext();
446 mid = mEnv->GetStaticMethodID(mActivityClass,
447 "getContext","()Landroid/content/Context;");
448 context = mEnv->CallStaticObjectMethod(mActivityClass, mid);
450 // assetManager = context.getAssets();
451 mid = mEnv->GetMethodID(mEnv->GetObjectClass(context),
452 "getAssets", "()Landroid/content/res/AssetManager;");
453 assetManager = mEnv->CallObjectMethod(context, mid);
455 // inputStream = assetManager.open(<filename>);
456 mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager),
457 "open", "(Ljava/lang/String;)Ljava/io/InputStream;");
458 inputStream = mEnv->CallObjectMethod(assetManager, mid, fileNameJString);
459 if (Android_JNI_ExceptionOccurred()) {
463 ctx->hidden.androidio.inputStream = inputStream;
464 ctx->hidden.androidio.inputStreamRef = mEnv->NewGlobalRef(inputStream);
466 // Despite all the visible documentation on [Asset]InputStream claiming
467 // that the .available() method is not guaranteed to return the entire file
468 // size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
469 // android/apis/content/ReadAsset.java imply that Android's
470 // AssetInputStream.available() /will/ always return the total file size
472 // size = inputStream.available();
473 mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
475 ctx->hidden.androidio.size = mEnv->CallIntMethod(inputStream, mid);
476 if (Android_JNI_ExceptionOccurred()) {
480 // readableByteChannel = Channels.newChannel(inputStream);
481 channels = mEnv->FindClass("java/nio/channels/Channels");
482 mid = mEnv->GetStaticMethodID(channels,
484 "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
485 readableByteChannel = mEnv->CallStaticObjectMethod(
486 channels, mid, inputStream);
487 if (Android_JNI_ExceptionOccurred()) {
491 ctx->hidden.androidio.readableByteChannel = readableByteChannel;
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);
516 extern "C" int Android_JNI_FileOpen(SDL_RWops* ctx,
517 const char* fileName, const char*)
519 LocalReferenceHolder refs;
521 if (!refs.init(mEnv)) {
529 jstring fileNameJString = mEnv->NewStringUTF(fileName);
530 ctx->hidden.androidio.fileName = fileNameJString;
531 ctx->hidden.androidio.fileNameRef = mEnv->NewGlobalRef(fileNameJString);
532 ctx->hidden.androidio.inputStreamRef = NULL;
534 return Android_JNI_FileOpen(ctx);
537 extern "C" size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
538 size_t size, size_t maxnum)
540 LocalReferenceHolder refs;
541 int bytesRemaining = size * maxnum;
544 if (!refs.init(mEnv)) {
548 jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannel;
549 jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
550 jobject byteBuffer = mEnv->NewDirectByteBuffer(buffer, bytesRemaining);
552 while (bytesRemaining > 0) {
553 // result = readableByteChannel.read(...);
554 int result = mEnv->CallIntMethod(readableByteChannel, readMethod, byteBuffer);
556 if (Android_JNI_ExceptionOccurred()) {
564 bytesRemaining -= result;
566 ctx->hidden.androidio.position += result;
569 return bytesRead / size;
572 extern "C" size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
573 size_t size, size_t num)
575 SDL_SetError("Cannot write to Android package filesystem");
579 static int Android_JNI_FileClose(SDL_RWops* ctx, bool release)
581 LocalReferenceHolder refs;
584 if (!refs.init(mEnv)) {
585 SDL_SetError("Failed to allocate enough JVM local references");
591 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
594 jobject inputStream = (jobject)ctx->hidden.androidio.inputStream;
596 // inputStream.close();
597 jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
599 mEnv->CallVoidMethod(inputStream, mid);
600 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
601 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
602 if (Android_JNI_ExceptionOccurred()) {
615 extern "C" long Android_JNI_FileSeek(SDL_RWops* ctx, long offset, int whence)
621 newPosition = offset;
624 newPosition = ctx->hidden.androidio.position + offset;
627 newPosition = ctx->hidden.androidio.size + offset;
630 SDL_SetError("Unknown value for 'whence'");
633 if (newPosition < 0) {
636 if (newPosition > ctx->hidden.androidio.size) {
637 newPosition = ctx->hidden.androidio.size;
640 long movement = newPosition - ctx->hidden.androidio.position;
641 jobject inputStream = (jobject)ctx->hidden.androidio.inputStream;
644 unsigned char buffer[1024];
646 // The easy case where we're seeking forwards
647 while (movement > 0) {
648 long amount = (long) sizeof (buffer);
649 if (amount > movement) {
652 size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount);
655 // Failed to read/skip the required amount, so fail
661 } else if (movement < 0) {
662 // We can't seek backwards so we have to reopen the file and seek
663 // forwards which obviously isn't very efficient
664 Android_JNI_FileClose(ctx, false);
665 Android_JNI_FileOpen(ctx);
666 Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
669 ctx->hidden.androidio.position = newPosition;
671 return ctx->hidden.androidio.position;
674 extern "C" int Android_JNI_FileClose(SDL_RWops* ctx)
676 return Android_JNI_FileClose(ctx, true);
679 #endif /* __ANDROID__ */
681 /* vi: set ts=4 sw=4 expandtab: */