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"
26 #include "SDL_android.h"
29 #include "../../events/SDL_events_c.h"
30 #include "../../video/android/SDL_androidkeyboard.h"
31 #include "../../video/android/SDL_androidtouch.h"
32 #include "../../video/android/SDL_androidvideo.h"
34 #include <android/log.h>
35 #define LOG_TAG "SDL_android"
36 //#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
37 //#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
38 #define LOGI(...) do {} while (false)
39 #define LOGE(...) do {} while (false)
42 /* Impelemented in audio/android/SDL_androidaudio.c */
43 extern void Android_RunAudioThread();
46 /*******************************************************************************
47 This file links the Java side of Android with libsdl
48 *******************************************************************************/
50 #include <android/log.h>
53 /*******************************************************************************
55 *******************************************************************************/
56 static JNIEnv* mEnv = NULL;
57 static JNIEnv* mAudioEnv = NULL;
58 static JavaVM* mJavaVM;
61 static jclass mActivityClass;
64 static jmethodID midCreateGLContext;
65 static jmethodID midFlipBuffers;
66 static jmethodID midAudioInit;
67 static jmethodID midAudioWriteShortBuffer;
68 static jmethodID midAudioWriteByteBuffer;
69 static jmethodID midAudioQuit;
71 // Accelerometer data storage
72 static float fLastAccelerometer[3];
75 /*******************************************************************************
76 Functions called by JNI
77 *******************************************************************************/
80 extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
84 LOGI("JNI_OnLoad called");
85 if (mJavaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
86 LOGE("Failed to get the environment using GetEnv()");
90 return JNI_VERSION_1_4;
93 // Called before SDL_main() to initialize JNI bindings
94 extern "C" void SDL_Android_Init(JNIEnv* env, jclass cls)
96 __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init()");
101 midCreateGLContext = mEnv->GetStaticMethodID(mActivityClass,
102 "createGLContext","(II)Z");
103 midFlipBuffers = mEnv->GetStaticMethodID(mActivityClass,
104 "flipBuffers","()V");
105 midAudioInit = mEnv->GetStaticMethodID(mActivityClass,
106 "audioInit", "(IZZI)Ljava/lang/Object;");
107 midAudioWriteShortBuffer = mEnv->GetStaticMethodID(mActivityClass,
108 "audioWriteShortBuffer", "([S)V");
109 midAudioWriteByteBuffer = mEnv->GetStaticMethodID(mActivityClass,
110 "audioWriteByteBuffer", "([B)V");
111 midAudioQuit = mEnv->GetStaticMethodID(mActivityClass,
114 if(!midCreateGLContext || !midFlipBuffers || !midAudioInit ||
115 !midAudioWriteShortBuffer || !midAudioWriteByteBuffer || !midAudioQuit) {
116 __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL: Couldn't locate Java callbacks, check that they're named and typed correctly");
118 __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init() finished!");
122 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeResize(
123 JNIEnv* env, jclass jcls,
124 jint width, jint height, jint format)
126 Android_SetScreenResolution(width, height, format);
130 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyDown(
131 JNIEnv* env, jclass jcls, jint keycode)
133 Android_OnKeyDown(keycode);
137 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyUp(
138 JNIEnv* env, jclass jcls, jint keycode)
140 Android_OnKeyUp(keycode);
144 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeTouch(
145 JNIEnv* env, jclass jcls,
146 jint touch_device_id_in, jint pointer_finger_id_in,
147 jint action, jfloat x, jfloat y, jfloat p)
149 Android_OnTouch(touch_device_id_in, pointer_finger_id_in, action, x, y, p);
153 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeAccel(
154 JNIEnv* env, jclass jcls,
155 jfloat x, jfloat y, jfloat z)
157 fLastAccelerometer[0] = x;
158 fLastAccelerometer[1] = y;
159 fLastAccelerometer[2] = z;
163 extern "C" void Java_org_libsdl_app_SDLActivity_nativeQuit(
164 JNIEnv* env, jclass cls)
166 // Inject a SDL_QUIT event
171 extern "C" void Java_org_libsdl_app_SDLActivity_nativePause(
172 JNIEnv* env, jclass cls)
174 if (Android_Window) {
175 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_HIDDEN, 0, 0);
176 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_LOST, 0, 0);
181 extern "C" void Java_org_libsdl_app_SDLActivity_nativeResume(
182 JNIEnv* env, jclass cls)
184 if (Android_Window) {
185 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_SHOWN, 0, 0);
186 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_GAINED, 0, 0);
190 extern "C" void Java_org_libsdl_app_SDLActivity_nativeRunAudioThread(
191 JNIEnv* env, jclass cls)
193 /* This is the audio thread, with a different environment */
196 Android_RunAudioThread();
200 /*******************************************************************************
201 Functions called by SDL into Java
202 *******************************************************************************/
203 extern "C" SDL_bool Android_JNI_CreateContext(int majorVersion, int minorVersion)
205 if (mEnv->CallStaticBooleanMethod(mActivityClass, midCreateGLContext, majorVersion, minorVersion)) {
212 extern "C" void Android_JNI_SwapWindow()
214 mEnv->CallStaticVoidMethod(mActivityClass, midFlipBuffers);
217 extern "C" void Android_JNI_SetActivityTitle(const char *title)
221 mid = mEnv->GetStaticMethodID(mActivityClass,"setActivityTitle","(Ljava/lang/String;)V");
223 mEnv->CallStaticVoidMethod(mActivityClass, mid, mEnv->NewStringUTF(title));
227 extern "C" void Android_JNI_GetAccelerometerValues(float values[3])
230 for (i = 0; i < 3; ++i) {
231 values[i] = fLastAccelerometer[i];
238 static jboolean audioBuffer16Bit = JNI_FALSE;
239 static jboolean audioBufferStereo = JNI_FALSE;
240 static jobject audioBuffer = NULL;
241 static void* audioBufferPinned = NULL;
243 extern "C" int Android_JNI_OpenAudioDevice(int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
245 int audioBufferFrames;
249 static bool isAttached = false;
250 status = mJavaVM->GetEnv((void **) &env, JNI_VERSION_1_4);
252 LOGE("callback_handler: failed to get JNI environment, assuming native thread");
253 status = mJavaVM->AttachCurrentThread(&env, NULL);
255 LOGE("callback_handler: failed to attach current thread");
262 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device");
263 audioBuffer16Bit = is16Bit;
264 audioBufferStereo = channelCount > 1;
266 audioBuffer = env->CallStaticObjectMethod(mActivityClass, midAudioInit, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames);
268 if (audioBuffer == NULL) {
269 __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: didn't get back a good audio buffer!");
272 audioBuffer = env->NewGlobalRef(audioBuffer);
274 jboolean isCopy = JNI_FALSE;
275 if (audioBuffer16Bit) {
276 audioBufferPinned = env->GetShortArrayElements((jshortArray)audioBuffer, &isCopy);
277 audioBufferFrames = env->GetArrayLength((jshortArray)audioBuffer);
279 audioBufferPinned = env->GetByteArrayElements((jbyteArray)audioBuffer, &isCopy);
280 audioBufferFrames = env->GetArrayLength((jbyteArray)audioBuffer);
282 if (audioBufferStereo) {
283 audioBufferFrames /= 2;
287 mJavaVM->DetachCurrentThread();
290 return audioBufferFrames;
293 extern "C" void * Android_JNI_GetAudioBuffer()
295 return audioBufferPinned;
298 extern "C" void Android_JNI_WriteAudioBuffer()
300 if (audioBuffer16Bit) {
301 mAudioEnv->ReleaseShortArrayElements((jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
302 mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
304 mAudioEnv->ReleaseByteArrayElements((jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT);
305 mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer);
308 /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
311 extern "C" void Android_JNI_CloseAudioDevice()
315 static bool isAttached = false;
316 status = mJavaVM->GetEnv((void **) &env, JNI_VERSION_1_4);
318 LOGE("callback_handler: failed to get JNI environment, assuming native thread");
319 status = mJavaVM->AttachCurrentThread(&env, NULL);
321 LOGE("callback_handler: failed to attach current thread");
327 env->CallStaticVoidMethod(mActivityClass, midAudioQuit);
330 env->DeleteGlobalRef(audioBuffer);
332 audioBufferPinned = NULL;
336 mJavaVM->DetachCurrentThread();
340 // Test for an exception and call SDL_SetError with its detail if one occurs
341 static bool Android_JNI_ExceptionOccurred()
343 jthrowable exception = mEnv->ExceptionOccurred();
344 if (exception != NULL) {
347 // Until this happens most JNI operations have undefined behaviour
348 mEnv->ExceptionClear();
350 jclass exceptionClass = mEnv->GetObjectClass(exception);
351 jclass classClass = mEnv->FindClass("java/lang/Class");
353 mid = mEnv->GetMethodID(classClass, "getName", "()Ljava/lang/String;");
354 jstring exceptionName = (jstring)mEnv->CallObjectMethod(exceptionClass, mid);
355 const char* exceptionNameUTF8 = mEnv->GetStringUTFChars(exceptionName, 0);
357 mid = mEnv->GetMethodID(exceptionClass, "getMessage", "()Ljava/lang/String;");
358 jstring exceptionMessage = (jstring)mEnv->CallObjectMethod(exception, mid);
360 if (exceptionMessage != NULL) {
361 const char* exceptionMessageUTF8 = mEnv->GetStringUTFChars(
362 exceptionMessage, 0);
363 SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
364 mEnv->ReleaseStringUTFChars(exceptionMessage, exceptionMessageUTF8);
365 mEnv->DeleteLocalRef(exceptionMessage);
367 SDL_SetError("%s", exceptionNameUTF8);
370 mEnv->ReleaseStringUTFChars(exceptionName, exceptionNameUTF8);
371 mEnv->DeleteLocalRef(exceptionName);
372 mEnv->DeleteLocalRef(classClass);
373 mEnv->DeleteLocalRef(exceptionClass);
374 mEnv->DeleteLocalRef(exception);
382 static int Android_JNI_FileOpen(SDL_RWops* ctx)
388 jobject assetManager;
391 jobject readableByteChannel;
392 jstring fileNameJString;
394 bool allocatedLocalFrame = false;
396 if (mEnv->PushLocalFrame(16) < 0) {
397 SDL_SetError("Failed to allocate enough JVM local references");
400 allocatedLocalFrame = true;
403 fileNameJString = (jstring)ctx->hidden.androidio.fileName;
405 // context = SDLActivity.getContext();
406 mid = mEnv->GetStaticMethodID(mActivityClass,
407 "getContext","()Landroid/content/Context;");
408 context = mEnv->CallStaticObjectMethod(mActivityClass, mid);
410 // assetManager = context.getAssets();
411 mid = mEnv->GetMethodID(mEnv->GetObjectClass(context),
412 "getAssets", "()Landroid/content/res/AssetManager;");
413 assetManager = mEnv->CallObjectMethod(context, mid);
415 // inputStream = assetManager.open(<filename>);
416 mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager),
417 "open", "(Ljava/lang/String;)Ljava/io/InputStream;");
418 inputStream = mEnv->CallObjectMethod(assetManager, mid, fileNameJString);
419 if (Android_JNI_ExceptionOccurred()) {
423 ctx->hidden.androidio.inputStream = inputStream;
424 ctx->hidden.androidio.inputStreamRef = mEnv->NewGlobalRef(inputStream);
426 // Despite all the visible documentation on [Asset]InputStream claiming
427 // that the .available() method is not guaranteed to return the entire file
428 // size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
429 // android/apis/content/ReadAsset.java imply that Android's
430 // AssetInputStream.available() /will/ always return the total file size
432 // size = inputStream.available();
433 mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
435 ctx->hidden.androidio.size = mEnv->CallIntMethod(inputStream, mid);
436 if (Android_JNI_ExceptionOccurred()) {
440 // readableByteChannel = Channels.newChannel(inputStream);
441 channels = mEnv->FindClass("java/nio/channels/Channels");
442 mid = mEnv->GetStaticMethodID(channels,
444 "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
445 readableByteChannel = mEnv->CallStaticObjectMethod(
446 channels, mid, inputStream);
447 if (Android_JNI_ExceptionOccurred()) {
451 ctx->hidden.androidio.readableByteChannel = readableByteChannel;
452 ctx->hidden.androidio.readableByteChannelRef =
453 mEnv->NewGlobalRef(readableByteChannel);
455 // Store .read id for reading purposes
456 mid = mEnv->GetMethodID(mEnv->GetObjectClass(readableByteChannel),
457 "read", "(Ljava/nio/ByteBuffer;)I");
458 ctx->hidden.androidio.readMethod = mid;
460 ctx->hidden.androidio.position = 0;
466 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
468 if(ctx->hidden.androidio.inputStreamRef != NULL) {
469 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
473 if (allocatedLocalFrame) {
474 mEnv->PopLocalFrame(NULL);
480 extern "C" int Android_JNI_FileOpen(SDL_RWops* ctx,
481 const char* fileName, const char*)
487 jstring fileNameJString = mEnv->NewStringUTF(fileName);
488 ctx->hidden.androidio.fileName = fileNameJString;
489 ctx->hidden.androidio.fileNameRef = mEnv->NewGlobalRef(fileNameJString);
490 ctx->hidden.androidio.inputStreamRef = NULL;
491 mEnv->DeleteLocalRef(fileNameJString);
493 return Android_JNI_FileOpen(ctx);
496 extern "C" size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
497 size_t size, size_t maxnum)
499 int bytesRemaining = size * maxnum;
502 jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannel;
503 jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
504 jobject byteBuffer = mEnv->NewDirectByteBuffer(buffer, bytesRemaining);
506 while (bytesRemaining > 0) {
507 // result = readableByteChannel.read(...);
508 int result = mEnv->CallIntMethod(readableByteChannel, readMethod, byteBuffer);
510 if (Android_JNI_ExceptionOccurred()) {
511 mEnv->DeleteLocalRef(byteBuffer);
519 bytesRemaining -= result;
521 ctx->hidden.androidio.position += result;
524 mEnv->DeleteLocalRef(byteBuffer);
526 return bytesRead / size;
529 extern "C" size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
530 size_t size, size_t num)
532 SDL_SetError("Cannot write to Android package filesystem");
536 static int Android_JNI_FileClose(SDL_RWops* ctx, bool release)
542 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
545 jobject inputStream = (jobject)ctx->hidden.androidio.inputStream;
547 // inputStream.close();
548 jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
550 mEnv->CallVoidMethod(inputStream, mid);
551 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
552 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
553 if (Android_JNI_ExceptionOccurred()) {
566 extern "C" long Android_JNI_FileSeek(SDL_RWops* ctx, long offset, int whence)
572 newPosition = offset;
575 newPosition = ctx->hidden.androidio.position + offset;
578 newPosition = ctx->hidden.androidio.size + offset;
581 SDL_SetError("Unknown value for 'whence'");
584 if (newPosition < 0) {
587 if (newPosition > ctx->hidden.androidio.size) {
588 newPosition = ctx->hidden.androidio.size;
591 long movement = newPosition - ctx->hidden.androidio.position;
592 jobject inputStream = (jobject)ctx->hidden.androidio.inputStream;
595 unsigned char buffer[1024];
597 // The easy case where we're seeking forwards
598 while (movement > 0) {
599 long amount = (long) sizeof (buffer);
600 if (amount > movement) {
603 size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount);
606 // Failed to read/skip the required amount, so fail
612 } else if (movement < 0) {
613 // We can't seek backwards so we have to reopen the file and seek
614 // forwards which obviously isn't very efficient
615 Android_JNI_FileClose(ctx, false);
616 Android_JNI_FileOpen(ctx);
617 Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
620 ctx->hidden.androidio.position = newPosition;
622 return ctx->hidden.androidio.position;
625 extern "C" int Android_JNI_FileClose(SDL_RWops* ctx)
627 return Android_JNI_FileClose(ctx, true);
630 #endif /* __ANDROID__ */
632 /* vi: set ts=4 sw=4 expandtab: */