2 Simple DirectMedia Layer
3 Copyright (C) 1997-2011 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
170 extern "C" void Java_org_libsdl_app_SDLActivity_nativeRunAudioThread(
171 JNIEnv* env, jclass cls)
173 /* This is the audio thread, with a different environment */
176 Android_RunAudioThread();
180 /*******************************************************************************
181 Functions called by SDL into Java
182 *******************************************************************************/
183 extern "C" SDL_bool Android_JNI_CreateContext(int majorVersion, int minorVersion)
185 if (mEnv->CallStaticBooleanMethod(mActivityClass, midCreateGLContext, majorVersion, minorVersion)) {
192 extern "C" void Android_JNI_SwapWindow()
194 mEnv->CallStaticVoidMethod(mActivityClass, midFlipBuffers);
197 extern "C" void Android_JNI_SetActivityTitle(const char *title)
201 mid = mEnv->GetStaticMethodID(mActivityClass,"setActivityTitle","(Ljava/lang/String;)V");
203 mEnv->CallStaticVoidMethod(mActivityClass, mid, mEnv->NewStringUTF(title));
207 extern "C" void Android_JNI_GetAccelerometerValues(float values[3])
210 for (i = 0; i < 3; ++i) {
211 values[i] = fLastAccelerometer[i];
218 static jboolean audioBuffer16Bit = JNI_FALSE;
219 static jboolean audioBufferStereo = JNI_FALSE;
220 static jobject audioBuffer = NULL;
221 static void* audioBufferPinned = NULL;
223 extern "C" int Android_JNI_OpenAudioDevice(int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
225 int audioBufferFrames;
229 static bool isAttached = false;
230 status = mJavaVM->GetEnv((void **) &env, JNI_VERSION_1_4);
232 LOGE("callback_handler: failed to get JNI environment, assuming native thread");
233 status = mJavaVM->AttachCurrentThread(&env, NULL);
235 LOGE("callback_handler: failed to attach current thread");
242 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device");
243 audioBuffer16Bit = is16Bit;
244 audioBufferStereo = channelCount > 1;
246 audioBuffer = env->CallStaticObjectMethod(mActivityClass, midAudioInit, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames);
248 if (audioBuffer == NULL) {
249 __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: didn't get back a good audio buffer!");
252 audioBuffer = env->NewGlobalRef(audioBuffer);
254 jboolean isCopy = JNI_FALSE;
255 if (audioBuffer16Bit) {
256 audioBufferPinned = env->GetShortArrayElements((jshortArray)audioBuffer, &isCopy);
257 audioBufferFrames = env->GetArrayLength((jshortArray)audioBuffer);
259 audioBufferPinned = env->GetByteArrayElements((jbyteArray)audioBuffer, &isCopy);
260 audioBufferFrames = env->GetArrayLength((jbyteArray)audioBuffer);
262 if (audioBufferStereo) {
263 audioBufferFrames /= 2;
267 mJavaVM->DetachCurrentThread();
270 return audioBufferFrames;
273 extern "C" void * Android_JNI_GetAudioBuffer()
275 return audioBufferPinned;
278 extern "C" void Android_JNI_WriteAudioBuffer()
280 if (audioBuffer16Bit) {
281 mAudioEnv->ReleaseShortArrayElements((jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
282 mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
284 mAudioEnv->ReleaseByteArrayElements((jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT);
285 mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer);
288 /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
291 extern "C" void Android_JNI_CloseAudioDevice()
295 static bool isAttached = false;
296 status = mJavaVM->GetEnv((void **) &env, JNI_VERSION_1_4);
298 LOGE("callback_handler: failed to get JNI environment, assuming native thread");
299 status = mJavaVM->AttachCurrentThread(&env, NULL);
301 LOGE("callback_handler: failed to attach current thread");
307 env->CallStaticVoidMethod(mActivityClass, midAudioQuit);
310 env->DeleteGlobalRef(audioBuffer);
312 audioBufferPinned = NULL;
316 mJavaVM->DetachCurrentThread();
320 // Test for an exception and call SDL_SetError with its detail if one occurs
321 static bool Android_JNI_ExceptionOccurred()
323 jthrowable exception = mEnv->ExceptionOccurred();
324 if (exception != NULL) {
327 // Until this happens most JNI operations have undefined behaviour
328 mEnv->ExceptionClear();
330 jclass exceptionClass = mEnv->GetObjectClass(exception);
331 jclass classClass = mEnv->FindClass("java/lang/Class");
333 mid = mEnv->GetMethodID(classClass, "getName", "()Ljava/lang/String;");
334 jstring exceptionName = (jstring)mEnv->CallObjectMethod(exceptionClass, mid);
335 const char* exceptionNameUTF8 = mEnv->GetStringUTFChars(exceptionName, 0);
337 mid = mEnv->GetMethodID(exceptionClass, "getMessage", "()Ljava/lang/String;");
338 jstring exceptionMessage = (jstring)mEnv->CallObjectMethod(exception, mid);
340 if (exceptionMessage != NULL) {
341 const char* exceptionMessageUTF8 = mEnv->GetStringUTFChars(
342 exceptionMessage, 0);
343 SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
344 mEnv->ReleaseStringUTFChars(exceptionMessage, exceptionMessageUTF8);
345 mEnv->DeleteLocalRef(exceptionMessage);
347 SDL_SetError("%s", exceptionNameUTF8);
350 mEnv->ReleaseStringUTFChars(exceptionName, exceptionNameUTF8);
351 mEnv->DeleteLocalRef(exceptionName);
352 mEnv->DeleteLocalRef(classClass);
353 mEnv->DeleteLocalRef(exceptionClass);
354 mEnv->DeleteLocalRef(exception);
362 static int Android_JNI_FileOpen(SDL_RWops* ctx)
368 jobject assetManager;
371 jobject readableByteChannel;
372 jstring fileNameJString;
374 bool allocatedLocalFrame = false;
376 if (mEnv->PushLocalFrame(16) < 0) {
377 SDL_SetError("Failed to allocate enough JVM local references");
380 allocatedLocalFrame = true;
383 fileNameJString = (jstring)ctx->hidden.androidio.fileName;
385 // context = SDLActivity.getContext();
386 mid = mEnv->GetStaticMethodID(mActivityClass,
387 "getContext","()Landroid/content/Context;");
388 context = mEnv->CallStaticObjectMethod(mActivityClass, mid);
390 // assetManager = context.getAssets();
391 mid = mEnv->GetMethodID(mEnv->GetObjectClass(context),
392 "getAssets", "()Landroid/content/res/AssetManager;");
393 assetManager = mEnv->CallObjectMethod(context, mid);
395 // inputStream = assetManager.open(<filename>);
396 mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager),
397 "open", "(Ljava/lang/String;)Ljava/io/InputStream;");
398 inputStream = mEnv->CallObjectMethod(assetManager, mid, fileNameJString);
399 if (Android_JNI_ExceptionOccurred()) {
403 ctx->hidden.androidio.inputStream = inputStream;
404 ctx->hidden.androidio.inputStreamRef = mEnv->NewGlobalRef(inputStream);
406 // Despite all the visible documentation on [Asset]InputStream claiming
407 // that the .available() method is not guaranteed to return the entire file
408 // size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
409 // android/apis/content/ReadAsset.java imply that Android's
410 // AssetInputStream.available() /will/ always return the total file size
412 // size = inputStream.available();
413 mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
415 ctx->hidden.androidio.size = mEnv->CallIntMethod(inputStream, mid);
416 if (Android_JNI_ExceptionOccurred()) {
420 // readableByteChannel = Channels.newChannel(inputStream);
421 channels = mEnv->FindClass("java/nio/channels/Channels");
422 mid = mEnv->GetStaticMethodID(channels,
424 "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
425 readableByteChannel = mEnv->CallStaticObjectMethod(
426 channels, mid, inputStream);
427 if (Android_JNI_ExceptionOccurred()) {
431 ctx->hidden.androidio.readableByteChannel = readableByteChannel;
432 ctx->hidden.androidio.readableByteChannelRef =
433 mEnv->NewGlobalRef(readableByteChannel);
435 // Store .read id for reading purposes
436 mid = mEnv->GetMethodID(mEnv->GetObjectClass(readableByteChannel),
437 "read", "(Ljava/nio/ByteBuffer;)I");
438 ctx->hidden.androidio.readMethod = mid;
440 ctx->hidden.androidio.position = 0;
446 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
448 if(ctx->hidden.androidio.inputStreamRef != NULL) {
449 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
453 if (allocatedLocalFrame) {
454 mEnv->PopLocalFrame(NULL);
460 extern "C" int Android_JNI_FileOpen(SDL_RWops* ctx,
461 const char* fileName, const char*)
467 jstring fileNameJString = mEnv->NewStringUTF(fileName);
468 ctx->hidden.androidio.fileName = fileNameJString;
469 ctx->hidden.androidio.fileNameRef = mEnv->NewGlobalRef(fileNameJString);
470 ctx->hidden.androidio.inputStreamRef = NULL;
471 mEnv->DeleteLocalRef(fileNameJString);
473 return Android_JNI_FileOpen(ctx);
476 extern "C" size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
477 size_t size, size_t maxnum)
479 int bytesRemaining = size * maxnum;
482 jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannel;
483 jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
484 jobject byteBuffer = mEnv->NewDirectByteBuffer(buffer, bytesRemaining);
486 while (bytesRemaining > 0) {
487 // result = readableByteChannel.read(...);
488 int result = mEnv->CallIntMethod(readableByteChannel, readMethod, byteBuffer);
490 if (Android_JNI_ExceptionOccurred()) {
491 mEnv->DeleteLocalRef(byteBuffer);
499 bytesRemaining -= result;
501 ctx->hidden.androidio.position += result;
504 mEnv->DeleteLocalRef(byteBuffer);
506 return bytesRead / size;
509 extern "C" size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
510 size_t size, size_t num)
512 SDL_SetError("Cannot write to Android package filesystem");
516 static int Android_JNI_FileClose(SDL_RWops* ctx, bool release)
522 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
525 jobject inputStream = (jobject)ctx->hidden.androidio.inputStream;
527 // inputStream.close();
528 jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
530 mEnv->CallVoidMethod(inputStream, mid);
531 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
532 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
533 if (Android_JNI_ExceptionOccurred()) {
546 extern "C" long Android_JNI_FileSeek(SDL_RWops* ctx, long offset, int whence)
552 newPosition = offset;
555 newPosition = ctx->hidden.androidio.position + offset;
558 newPosition = ctx->hidden.androidio.size + offset;
561 SDL_SetError("Unknown value for 'whence'");
564 if (newPosition < 0) {
567 if (newPosition > ctx->hidden.androidio.size) {
568 newPosition = ctx->hidden.androidio.size;
571 long movement = newPosition - ctx->hidden.androidio.position;
572 jobject inputStream = (jobject)ctx->hidden.androidio.inputStream;
575 unsigned char buffer[1024];
577 // The easy case where we're seeking forwards
578 while (movement > 0) {
579 long amount = (long) sizeof (buffer);
580 if (amount > movement) {
583 size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount);
586 // Failed to read/skip the required amount, so fail
592 } else if (movement < 0) {
593 // We can't seek backwards so we have to reopen the file and seek
594 // forwards which obviously isn't very efficient
595 Android_JNI_FileClose(ctx, false);
596 Android_JNI_FileOpen(ctx);
597 Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
600 ctx->hidden.androidio.position = newPosition;
602 return ctx->hidden.androidio.position;
605 extern "C" int Android_JNI_FileClose(SDL_RWops* ctx)
607 return Android_JNI_FileClose(ctx, true);
610 #endif /* __ANDROID__ */
612 /* vi: set ts=4 sw=4 expandtab: */