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"
28 #include "SDL_system.h"
29 #include "SDL_android.h"
33 #include "../../events/SDL_events_c.h"
34 #include "../../video/android/SDL_androidkeyboard.h"
35 #include "../../video/android/SDL_androidtouch.h"
36 #include "../../video/android/SDL_androidvideo.h"
38 #include <android/log.h>
40 #include <sys/types.h>
42 #define LOG_TAG "SDL_android"
43 //#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
44 //#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
45 #define LOGI(...) do {} while (false)
46 #define LOGE(...) do {} while (false)
48 /* Uncomment this to log messages entering and exiting methods in this file */
51 /* Implemented in audio/android/SDL_androidaudio.c */
52 extern void Android_RunAudioThread();
55 /*******************************************************************************
56 This file links the Java side of Android with libsdl
57 *******************************************************************************/
59 #include <android/log.h>
62 /*******************************************************************************
64 *******************************************************************************/
65 static pthread_key_t mThreadKey;
66 static JavaVM* mJavaVM;
69 static jclass mActivityClass;
72 static jmethodID midCreateGLContext;
73 static jmethodID midFlipBuffers;
74 static jmethodID midAudioInit;
75 static jmethodID midAudioWriteShortBuffer;
76 static jmethodID midAudioWriteByteBuffer;
77 static jmethodID midAudioQuit;
79 // Accelerometer data storage
80 static float fLastAccelerometer[3];
81 static bool bHasNewData;
83 /*******************************************************************************
84 Functions called by JNI
85 *******************************************************************************/
88 extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
92 LOGI("JNI_OnLoad called");
93 if (mJavaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
94 LOGE("Failed to get the environment using GetEnv()");
98 * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread
99 * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this
101 if (pthread_key_create(&mThreadKey, Android_JNI_ThreadDestroyed)) {
102 __android_log_print(ANDROID_LOG_ERROR, "SDL", "Error initializing pthread key");
105 Android_JNI_SetupThread();
108 return JNI_VERSION_1_4;
111 // Called before SDL_main() to initialize JNI bindings
112 extern "C" void SDL_Android_Init(JNIEnv* mEnv, jclass cls)
114 __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init()");
116 Android_JNI_SetupThread();
118 mActivityClass = (jclass)mEnv->NewGlobalRef(cls);
120 midCreateGLContext = mEnv->GetStaticMethodID(mActivityClass,
121 "createGLContext","(II[I)Z");
122 midFlipBuffers = mEnv->GetStaticMethodID(mActivityClass,
123 "flipBuffers","()V");
124 midAudioInit = mEnv->GetStaticMethodID(mActivityClass,
125 "audioInit", "(IZZI)V");
126 midAudioWriteShortBuffer = mEnv->GetStaticMethodID(mActivityClass,
127 "audioWriteShortBuffer", "([S)V");
128 midAudioWriteByteBuffer = mEnv->GetStaticMethodID(mActivityClass,
129 "audioWriteByteBuffer", "([B)V");
130 midAudioQuit = mEnv->GetStaticMethodID(mActivityClass,
135 if(!midCreateGLContext || !midFlipBuffers || !midAudioInit ||
136 !midAudioWriteShortBuffer || !midAudioWriteByteBuffer || !midAudioQuit) {
137 __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL: Couldn't locate Java callbacks, check that they're named and typed correctly");
139 __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init() finished!");
143 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeResize(
144 JNIEnv* env, jclass jcls,
145 jint width, jint height, jint format)
147 Android_SetScreenResolution(width, height, format);
151 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyDown(
152 JNIEnv* env, jclass jcls, jint keycode)
154 Android_OnKeyDown(keycode);
158 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyUp(
159 JNIEnv* env, jclass jcls, jint keycode)
161 Android_OnKeyUp(keycode);
165 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeTouch(
166 JNIEnv* env, jclass jcls,
167 jint touch_device_id_in, jint pointer_finger_id_in,
168 jint action, jfloat x, jfloat y, jfloat p)
170 Android_OnTouch(touch_device_id_in, pointer_finger_id_in, action, x, y, p);
174 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeAccel(
175 JNIEnv* env, jclass jcls,
176 jfloat x, jfloat y, jfloat z)
178 fLastAccelerometer[0] = x;
179 fLastAccelerometer[1] = y;
180 fLastAccelerometer[2] = z;
185 extern "C" void Java_org_libsdl_app_SDLActivity_nativeQuit(
186 JNIEnv* env, jclass cls)
188 // Inject a SDL_QUIT event
193 extern "C" void Java_org_libsdl_app_SDLActivity_nativePause(
194 JNIEnv* env, jclass cls)
196 if (Android_Window) {
197 /* Signal the pause semaphore so the event loop knows to pause and (optionally) block itself */
198 if (!SDL_SemValue(Android_PauseSem)) SDL_SemPost(Android_PauseSem);
199 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_LOST, 0, 0);
200 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_MINIMIZED, 0, 0);
205 extern "C" void Java_org_libsdl_app_SDLActivity_nativeResume(
206 JNIEnv* env, jclass cls)
208 if (Android_Window) {
209 /* Signal the resume semaphore so the event loop knows to resume and restore the GL Context
210 * We can't restore the GL Context here because it needs to be done on the SDL main thread
211 * and this function will be called from the Java thread instead.
213 if (!SDL_SemValue(Android_ResumeSem)) SDL_SemPost(Android_ResumeSem);
214 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_GAINED, 0, 0);
215 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_RESTORED, 0, 0);
219 extern "C" void Java_org_libsdl_app_SDLActivity_nativeRunAudioThread(
220 JNIEnv* env, jclass cls)
222 /* This is the audio thread, with a different environment */
223 Android_JNI_SetupThread();
225 Android_RunAudioThread();
228 extern "C" void Java_org_libsdl_app_SDLInputConnection_nativeCommitText(
229 JNIEnv* env, jclass cls,
230 jstring text, jint newCursorPosition)
232 const char *utftext = env->GetStringUTFChars(text, NULL);
234 SDL_SendKeyboardText(utftext);
236 env->ReleaseStringUTFChars(text, utftext);
239 extern "C" void Java_org_libsdl_app_SDLInputConnection_nativeSetComposingText(
240 JNIEnv* env, jclass cls,
241 jstring text, jint newCursorPosition)
243 const char *utftext = env->GetStringUTFChars(text, NULL);
245 SDL_SendEditingText(utftext, 0, 0);
247 env->ReleaseStringUTFChars(text, utftext);
252 /*******************************************************************************
253 Functions called by SDL into Java
254 *******************************************************************************/
256 class LocalReferenceHolder
262 static bool IsActive() {
267 LocalReferenceHolder(const char *func) : m_env(NULL), m_func(func) {
269 SDL_Log("Entering function %s", m_func);
272 ~LocalReferenceHolder() {
274 SDL_Log("Leaving function %s", m_func);
277 m_env->PopLocalFrame(NULL);
282 bool init(JNIEnv *env, jint capacity = 16) {
283 if (env->PushLocalFrame(capacity) < 0) {
284 SDL_SetError("Failed to allocate enough JVM local references");
296 int LocalReferenceHolder::s_active;
298 extern "C" SDL_bool Android_JNI_CreateContext(int majorVersion, int minorVersion,
299 int red, int green, int blue, int alpha,
300 int buffer, int depth, int stencil,
301 int buffers, int samples)
303 JNIEnv *env = Android_JNI_GetEnv();
307 EGL_GREEN_SIZE, green,
309 EGL_ALPHA_SIZE, alpha,
310 EGL_BUFFER_SIZE, buffer,
311 EGL_DEPTH_SIZE, depth,
312 EGL_STENCIL_SIZE, stencil,
313 EGL_SAMPLE_BUFFERS, buffers,
314 EGL_SAMPLES, samples,
315 EGL_RENDERABLE_TYPE, (majorVersion == 1 ? EGL_OPENGL_ES_BIT : EGL_OPENGL_ES2_BIT),
318 int len = SDL_arraysize(attribs);
322 array = env->NewIntArray(len);
323 env->SetIntArrayRegion(array, 0, len, attribs);
325 jboolean success = env->CallStaticBooleanMethod(mActivityClass, midCreateGLContext, majorVersion, minorVersion, array);
327 env->DeleteLocalRef(array);
329 return success ? SDL_TRUE : SDL_FALSE;
332 extern "C" void Android_JNI_SwapWindow()
334 JNIEnv *mEnv = Android_JNI_GetEnv();
335 mEnv->CallStaticVoidMethod(mActivityClass, midFlipBuffers);
338 extern "C" void Android_JNI_SetActivityTitle(const char *title)
341 JNIEnv *mEnv = Android_JNI_GetEnv();
342 mid = mEnv->GetStaticMethodID(mActivityClass,"setActivityTitle","(Ljava/lang/String;)V");
344 jstring jtitle = reinterpret_cast<jstring>(mEnv->NewStringUTF(title));
345 mEnv->CallStaticVoidMethod(mActivityClass, mid, jtitle);
346 mEnv->DeleteLocalRef(jtitle);
350 extern "C" SDL_bool Android_JNI_GetAccelerometerValues(float values[3])
353 SDL_bool retval = SDL_FALSE;
356 for (i = 0; i < 3; ++i) {
357 values[i] = fLastAccelerometer[i];
366 static void Android_JNI_ThreadDestroyed(void* value) {
367 /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */
368 JNIEnv *env = (JNIEnv*) value;
370 mJavaVM->DetachCurrentThread();
371 pthread_setspecific(mThreadKey, NULL);
375 JNIEnv* Android_JNI_GetEnv(void) {
376 /* From http://developer.android.com/guide/practices/jni.html
377 * All threads are Linux threads, scheduled by the kernel.
378 * They're usually started from managed code (using Thread.start), but they can also be created elsewhere and then
379 * attached to the JavaVM. For example, a thread started with pthread_create can be attached with the
380 * JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a thread is attached, it has no JNIEnv,
381 * and cannot make JNI calls.
382 * Attaching a natively-created thread causes a java.lang.Thread object to be constructed and added to the "main"
383 * ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread on an already-attached thread
385 * Note: You can call this function any number of times for the same thread, there's no harm in it
389 int status = mJavaVM->AttachCurrentThread(&env, NULL);
391 LOGE("failed to attach current thread");
398 int Android_JNI_SetupThread(void) {
399 /* From http://developer.android.com/guide/practices/jni.html
400 * Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward,
401 * in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be
402 * called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific
403 * to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.)
404 * Note: The destructor is not called unless the stored value is != NULL
405 * Note: You can call this function any number of times for the same thread, there's no harm in it
406 * (except for some lost CPU cycles)
408 JNIEnv *env = Android_JNI_GetEnv();
409 pthread_setspecific(mThreadKey, (void*) env);
416 static jboolean audioBuffer16Bit = JNI_FALSE;
417 static jboolean audioBufferStereo = JNI_FALSE;
418 static jobject audioBuffer = NULL;
419 static void* audioBufferPinned = NULL;
421 extern "C" int Android_JNI_OpenAudioDevice(int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
423 int audioBufferFrames;
426 JNIEnv *env = Android_JNI_GetEnv();
429 LOGE("callback_handler: failed to attach current thread");
431 Android_JNI_SetupThread();
434 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device");
435 audioBuffer16Bit = is16Bit;
436 audioBufferStereo = channelCount > 1;
438 env->CallStaticObjectMethod(mActivityClass, midAudioInit, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames);
440 /* Allocating the audio buffer from the Java side and passing it as the return value for audioInit no longer works on
441 * Android >= 4.2 due to a "stale global reference" error. So now we allocate this buffer directly from this side. */
444 jshortArray audioBufferLocal = env->NewShortArray(desiredBufferFrames * (audioBufferStereo ? 2 : 1));
445 if (audioBufferLocal) {
446 audioBuffer = env->NewGlobalRef(audioBufferLocal);
447 env->DeleteLocalRef(audioBufferLocal);
451 jbyteArray audioBufferLocal = env->NewByteArray(desiredBufferFrames * (audioBufferStereo ? 2 : 1));
452 if (audioBufferLocal) {
453 audioBuffer = env->NewGlobalRef(audioBufferLocal);
454 env->DeleteLocalRef(audioBufferLocal);
458 if (audioBuffer == NULL) {
459 __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: could not allocate an audio buffer!");
463 jboolean isCopy = JNI_FALSE;
464 if (audioBuffer16Bit) {
465 audioBufferPinned = env->GetShortArrayElements((jshortArray)audioBuffer, &isCopy);
466 audioBufferFrames = env->GetArrayLength((jshortArray)audioBuffer);
468 audioBufferPinned = env->GetByteArrayElements((jbyteArray)audioBuffer, &isCopy);
469 audioBufferFrames = env->GetArrayLength((jbyteArray)audioBuffer);
471 if (audioBufferStereo) {
472 audioBufferFrames /= 2;
475 return audioBufferFrames;
478 extern "C" void * Android_JNI_GetAudioBuffer()
480 return audioBufferPinned;
483 extern "C" void Android_JNI_WriteAudioBuffer()
485 JNIEnv *mAudioEnv = Android_JNI_GetEnv();
487 if (audioBuffer16Bit) {
488 mAudioEnv->ReleaseShortArrayElements((jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
489 mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
491 mAudioEnv->ReleaseByteArrayElements((jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT);
492 mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer);
495 /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
498 extern "C" void Android_JNI_CloseAudioDevice()
501 JNIEnv *env = Android_JNI_GetEnv();
503 env->CallStaticVoidMethod(mActivityClass, midAudioQuit);
506 env->DeleteGlobalRef(audioBuffer);
508 audioBufferPinned = NULL;
512 // Test for an exception and call SDL_SetError with its detail if one occurs
513 static bool Android_JNI_ExceptionOccurred()
515 SDL_assert(LocalReferenceHolder::IsActive());
516 JNIEnv *mEnv = Android_JNI_GetEnv();
518 jthrowable exception = mEnv->ExceptionOccurred();
519 if (exception != NULL) {
522 // Until this happens most JNI operations have undefined behaviour
523 mEnv->ExceptionClear();
525 jclass exceptionClass = mEnv->GetObjectClass(exception);
526 jclass classClass = mEnv->FindClass("java/lang/Class");
528 mid = mEnv->GetMethodID(classClass, "getName", "()Ljava/lang/String;");
529 jstring exceptionName = (jstring)mEnv->CallObjectMethod(exceptionClass, mid);
530 const char* exceptionNameUTF8 = mEnv->GetStringUTFChars(exceptionName, 0);
532 mid = mEnv->GetMethodID(exceptionClass, "getMessage", "()Ljava/lang/String;");
533 jstring exceptionMessage = (jstring)mEnv->CallObjectMethod(exception, mid);
535 if (exceptionMessage != NULL) {
536 const char* exceptionMessageUTF8 = mEnv->GetStringUTFChars(
537 exceptionMessage, 0);
538 SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
539 mEnv->ReleaseStringUTFChars(exceptionMessage, exceptionMessageUTF8);
541 SDL_SetError("%s", exceptionNameUTF8);
544 mEnv->ReleaseStringUTFChars(exceptionName, exceptionNameUTF8);
552 static int Android_JNI_FileOpen(SDL_RWops* ctx)
554 LocalReferenceHolder refs(__FUNCTION__);
559 jobject assetManager;
562 jobject readableByteChannel;
563 jstring fileNameJString;
568 JNIEnv *mEnv = Android_JNI_GetEnv();
569 if (!refs.init(mEnv)) {
573 fileNameJString = (jstring)ctx->hidden.androidio.fileNameRef;
574 ctx->hidden.androidio.position = 0;
576 // context = SDLActivity.getContext();
577 mid = mEnv->GetStaticMethodID(mActivityClass,
578 "getContext","()Landroid/content/Context;");
579 context = mEnv->CallStaticObjectMethod(mActivityClass, mid);
582 // assetManager = context.getAssets();
583 mid = mEnv->GetMethodID(mEnv->GetObjectClass(context),
584 "getAssets", "()Landroid/content/res/AssetManager;");
585 assetManager = mEnv->CallObjectMethod(context, mid);
587 /* First let's try opening the file to obtain an AssetFileDescriptor.
588 * This method reads the files directly from the APKs using standard *nix calls
590 mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager), "openFd", "(Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;");
591 inputStream = mEnv->CallObjectMethod(assetManager, mid, fileNameJString);
592 if (Android_JNI_ExceptionOccurred()) {
596 mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream), "getStartOffset", "()J");
597 ctx->hidden.androidio.offset = mEnv->CallLongMethod(inputStream, mid);
598 if (Android_JNI_ExceptionOccurred()) {
602 mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream), "getDeclaredLength", "()J");
603 ctx->hidden.androidio.size = mEnv->CallLongMethod(inputStream, mid);
604 if (Android_JNI_ExceptionOccurred()) {
608 mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream), "getFileDescriptor", "()Ljava/io/FileDescriptor;");
609 fd = mEnv->CallObjectMethod(inputStream, mid);
610 fdCls = mEnv->GetObjectClass(fd);
611 descriptor = mEnv->GetFieldID(fdCls, "descriptor", "I");
612 ctx->hidden.androidio.fd = mEnv->GetIntField(fd, descriptor);
613 ctx->hidden.androidio.assetFileDescriptorRef = mEnv->NewGlobalRef(inputStream);
615 // Seek to the correct offset in the file.
616 lseek(ctx->hidden.androidio.fd, (off_t)ctx->hidden.androidio.offset, SEEK_SET);
620 __android_log_print(ANDROID_LOG_DEBUG, "SDL", "Falling back to legacy InputStream method for opening file");
621 /* Try the old method using InputStream */
622 ctx->hidden.androidio.assetFileDescriptorRef = NULL;
624 // inputStream = assetManager.open(<filename>);
625 mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager),
626 "open", "(Ljava/lang/String;I)Ljava/io/InputStream;");
627 inputStream = mEnv->CallObjectMethod(assetManager, mid, fileNameJString, 1 /*ACCESS_RANDOM*/);
628 if (Android_JNI_ExceptionOccurred()) {
632 ctx->hidden.androidio.inputStreamRef = mEnv->NewGlobalRef(inputStream);
634 // Despite all the visible documentation on [Asset]InputStream claiming
635 // that the .available() method is not guaranteed to return the entire file
636 // size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
637 // android/apis/content/ReadAsset.java imply that Android's
638 // AssetInputStream.available() /will/ always return the total file size
640 // size = inputStream.available();
641 mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
643 ctx->hidden.androidio.size = (long)mEnv->CallIntMethod(inputStream, mid);
644 if (Android_JNI_ExceptionOccurred()) {
648 // readableByteChannel = Channels.newChannel(inputStream);
649 channels = mEnv->FindClass("java/nio/channels/Channels");
650 mid = mEnv->GetStaticMethodID(channels,
652 "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
653 readableByteChannel = mEnv->CallStaticObjectMethod(
654 channels, mid, inputStream);
655 if (Android_JNI_ExceptionOccurred()) {
659 ctx->hidden.androidio.readableByteChannelRef =
660 mEnv->NewGlobalRef(readableByteChannel);
662 // Store .read id for reading purposes
663 mid = mEnv->GetMethodID(mEnv->GetObjectClass(readableByteChannel),
664 "read", "(Ljava/nio/ByteBuffer;)I");
665 ctx->hidden.androidio.readMethod = mid;
672 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
674 if(ctx->hidden.androidio.inputStreamRef != NULL) {
675 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
678 if(ctx->hidden.androidio.readableByteChannelRef != NULL) {
679 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
682 if(ctx->hidden.androidio.assetFileDescriptorRef != NULL) {
683 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.assetFileDescriptorRef);
691 extern "C" int Android_JNI_FileOpen(SDL_RWops* ctx,
692 const char* fileName, const char*)
694 LocalReferenceHolder refs(__FUNCTION__);
695 JNIEnv *mEnv = Android_JNI_GetEnv();
697 if (!refs.init(mEnv)) {
705 jstring fileNameJString = mEnv->NewStringUTF(fileName);
706 ctx->hidden.androidio.fileNameRef = mEnv->NewGlobalRef(fileNameJString);
707 ctx->hidden.androidio.inputStreamRef = NULL;
708 ctx->hidden.androidio.readableByteChannelRef = NULL;
709 ctx->hidden.androidio.readMethod = NULL;
710 ctx->hidden.androidio.assetFileDescriptorRef = NULL;
712 return Android_JNI_FileOpen(ctx);
715 extern "C" size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
716 size_t size, size_t maxnum)
718 LocalReferenceHolder refs(__FUNCTION__);
720 if (ctx->hidden.androidio.assetFileDescriptorRef) {
721 size_t bytesMax = size * maxnum;
722 if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && ctx->hidden.androidio.position + bytesMax > ctx->hidden.androidio.size) {
723 bytesMax = ctx->hidden.androidio.size - ctx->hidden.androidio.position;
725 size_t result = read(ctx->hidden.androidio.fd, buffer, bytesMax );
727 ctx->hidden.androidio.position += result;
728 return result / size;
732 jlong bytesRemaining = (jlong) (size * maxnum);
733 jlong bytesMax = (jlong) (ctx->hidden.androidio.size - ctx->hidden.androidio.position);
736 /* Don't read more bytes than those that remain in the file, otherwise we get an exception */
737 if (bytesRemaining > bytesMax) bytesRemaining = bytesMax;
739 JNIEnv *mEnv = Android_JNI_GetEnv();
740 if (!refs.init(mEnv)) {
744 jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannelRef;
745 jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
746 jobject byteBuffer = mEnv->NewDirectByteBuffer(buffer, bytesRemaining);
748 while (bytesRemaining > 0) {
749 // result = readableByteChannel.read(...);
750 int result = mEnv->CallIntMethod(readableByteChannel, readMethod, byteBuffer);
752 if (Android_JNI_ExceptionOccurred()) {
760 bytesRemaining -= result;
762 ctx->hidden.androidio.position += result;
764 return bytesRead / size;
768 extern "C" size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
769 size_t size, size_t num)
771 SDL_SetError("Cannot write to Android package filesystem");
775 static int Android_JNI_FileClose(SDL_RWops* ctx, bool release)
777 LocalReferenceHolder refs(__FUNCTION__);
779 JNIEnv *mEnv = Android_JNI_GetEnv();
781 if (!refs.init(mEnv)) {
782 SDL_SetError("Failed to allocate enough JVM local references");
788 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
791 if (ctx->hidden.androidio.assetFileDescriptorRef) {
792 jobject inputStream = (jobject)ctx->hidden.androidio.assetFileDescriptorRef;
793 jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
795 mEnv->CallVoidMethod(inputStream, mid);
796 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.assetFileDescriptorRef);
797 if (Android_JNI_ExceptionOccurred()) {
802 jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
804 // inputStream.close();
805 jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
807 mEnv->CallVoidMethod(inputStream, mid);
808 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
809 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
810 if (Android_JNI_ExceptionOccurred()) {
824 extern "C" Sint64 Android_JNI_FileSize(SDL_RWops* ctx)
826 return ctx->hidden.androidio.size;
829 extern "C" Sint64 Android_JNI_FileSeek(SDL_RWops* ctx, Sint64 offset, int whence)
831 if (ctx->hidden.androidio.assetFileDescriptorRef) {
834 if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
835 offset += ctx->hidden.androidio.offset;
838 offset += ctx->hidden.androidio.position;
839 if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
840 offset += ctx->hidden.androidio.offset;
843 offset = ctx->hidden.androidio.offset + ctx->hidden.androidio.size + offset;
846 SDL_SetError("Unknown value for 'whence'");
851 off_t ret = lseek(ctx->hidden.androidio.fd, (off_t)offset, SEEK_SET);
852 if (ret == -1) return -1;
853 ctx->hidden.androidio.position = ret - ctx->hidden.androidio.offset;
859 newPosition = offset;
862 newPosition = ctx->hidden.androidio.position + offset;
865 newPosition = ctx->hidden.androidio.size + offset;
868 SDL_SetError("Unknown value for 'whence'");
872 /* Validate the new position */
873 if (newPosition < 0) {
874 SDL_Error(SDL_EFSEEK);
877 if (newPosition > ctx->hidden.androidio.size) {
878 newPosition = ctx->hidden.androidio.size;
881 Sint64 movement = newPosition - ctx->hidden.androidio.position;
883 unsigned char buffer[4096];
885 // The easy case where we're seeking forwards
886 while (movement > 0) {
887 Sint64 amount = sizeof (buffer);
888 if (amount > movement) {
891 size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount);
893 // Failed to read/skip the required amount, so fail
900 } else if (movement < 0) {
901 // We can't seek backwards so we have to reopen the file and seek
902 // forwards which obviously isn't very efficient
903 Android_JNI_FileClose(ctx, false);
904 Android_JNI_FileOpen(ctx);
905 Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
909 return ctx->hidden.androidio.position;
913 extern "C" int Android_JNI_FileClose(SDL_RWops* ctx)
915 return Android_JNI_FileClose(ctx, true);
918 // returns a new global reference which needs to be released later
919 static jobject Android_JNI_GetSystemServiceObject(const char* name)
921 LocalReferenceHolder refs(__FUNCTION__);
922 JNIEnv* env = Android_JNI_GetEnv();
923 if (!refs.init(env)) {
927 jstring service = env->NewStringUTF(name);
931 mid = env->GetStaticMethodID(mActivityClass, "getContext", "()Landroid/content/Context;");
932 jobject context = env->CallStaticObjectMethod(mActivityClass, mid);
934 mid = env->GetMethodID(mActivityClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
935 jobject manager = env->CallObjectMethod(context, mid, service);
937 env->DeleteLocalRef(service);
939 return manager ? env->NewGlobalRef(manager) : NULL;
942 #define SETUP_CLIPBOARD(error) \
943 LocalReferenceHolder refs(__FUNCTION__); \
944 JNIEnv* env = Android_JNI_GetEnv(); \
945 if (!refs.init(env)) { \
948 jobject clipboard = Android_JNI_GetSystemServiceObject("clipboard"); \
953 extern "C" int Android_JNI_SetClipboardText(const char* text)
957 jmethodID mid = env->GetMethodID(env->GetObjectClass(clipboard), "setText", "(Ljava/lang/CharSequence;)V");
958 jstring string = env->NewStringUTF(text);
959 env->CallVoidMethod(clipboard, mid, string);
960 env->DeleteGlobalRef(clipboard);
961 env->DeleteLocalRef(string);
965 extern "C" char* Android_JNI_GetClipboardText()
967 SETUP_CLIPBOARD(SDL_strdup(""))
969 jmethodID mid = env->GetMethodID(env->GetObjectClass(clipboard), "getText", "()Ljava/lang/CharSequence;");
970 jobject sequence = env->CallObjectMethod(clipboard, mid);
971 env->DeleteGlobalRef(clipboard);
973 mid = env->GetMethodID(env->GetObjectClass(sequence), "toString", "()Ljava/lang/String;");
974 jstring string = reinterpret_cast<jstring>(env->CallObjectMethod(sequence, mid));
975 const char* utf = env->GetStringUTFChars(string, 0);
977 char* text = SDL_strdup(utf);
978 env->ReleaseStringUTFChars(string, utf);
982 return SDL_strdup("");
985 extern "C" SDL_bool Android_JNI_HasClipboardText()
987 SETUP_CLIPBOARD(SDL_FALSE)
989 jmethodID mid = env->GetMethodID(env->GetObjectClass(clipboard), "hasText", "()Z");
990 jboolean has = env->CallBooleanMethod(clipboard, mid);
991 env->DeleteGlobalRef(clipboard);
992 return has ? SDL_TRUE : SDL_FALSE;
996 // returns 0 on success or -1 on error (others undefined then)
997 // returns truthy or falsy value in plugged, charged and battery
998 // returns the value in seconds and percent or -1 if not available
999 extern "C" int Android_JNI_GetPowerInfo(int* plugged, int* charged, int* battery, int* seconds, int* percent)
1001 LocalReferenceHolder refs(__FUNCTION__);
1002 JNIEnv* env = Android_JNI_GetEnv();
1003 if (!refs.init(env)) {
1009 mid = env->GetStaticMethodID(mActivityClass, "getContext", "()Landroid/content/Context;");
1010 jobject context = env->CallStaticObjectMethod(mActivityClass, mid);
1012 jstring action = env->NewStringUTF("android.intent.action.BATTERY_CHANGED");
1014 jclass cls = env->FindClass("android/content/IntentFilter");
1016 mid = env->GetMethodID(cls, "<init>", "(Ljava/lang/String;)V");
1017 jobject filter = env->NewObject(cls, mid, action);
1019 env->DeleteLocalRef(action);
1021 mid = env->GetMethodID(mActivityClass, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;");
1022 jobject intent = env->CallObjectMethod(context, mid, NULL, filter);
1024 env->DeleteLocalRef(filter);
1026 cls = env->GetObjectClass(intent);
1029 jmethodID imid = env->GetMethodID(cls, "getIntExtra", "(Ljava/lang/String;I)I");
1031 #define GET_INT_EXTRA(var, key) \
1032 iname = env->NewStringUTF(key); \
1033 int var = env->CallIntMethod(intent, imid, iname, -1); \
1034 env->DeleteLocalRef(iname);
1037 jmethodID bmid = env->GetMethodID(cls, "getBooleanExtra", "(Ljava/lang/String;Z)Z");
1039 #define GET_BOOL_EXTRA(var, key) \
1040 bname = env->NewStringUTF(key); \
1041 int var = env->CallBooleanMethod(intent, bmid, bname, JNI_FALSE); \
1042 env->DeleteLocalRef(bname);
1045 GET_INT_EXTRA(plug, "plugged") // == BatteryManager.EXTRA_PLUGGED (API 5)
1049 // 1 == BatteryManager.BATTERY_PLUGGED_AC
1050 // 2 == BatteryManager.BATTERY_PLUGGED_USB
1051 *plugged = (0 < plug) ? 1 : 0;
1055 GET_INT_EXTRA(status, "status") // == BatteryManager.EXTRA_STATUS (API 5)
1059 // 5 == BatteryManager.BATTERY_STATUS_FULL
1060 *charged = (status == 5) ? 1 : 0;
1064 GET_BOOL_EXTRA(present, "present") // == BatteryManager.EXTRA_PRESENT (API 5)
1065 *battery = present ? 1 : 0;
1069 *seconds = -1; // not possible
1073 GET_INT_EXTRA(level, "level") // == BatteryManager.EXTRA_LEVEL (API 5)
1074 GET_INT_EXTRA(scale, "scale") // == BatteryManager.EXTRA_SCALE (API 5)
1075 if ((level == -1) || (scale == -1)) {
1078 *percent = level * 100 / scale;
1081 env->DeleteLocalRef(intent);
1086 // sends message to be handled on the UI event dispatch thread
1087 extern "C" int Android_JNI_SendMessage(int command, int param)
1089 JNIEnv *env = Android_JNI_GetEnv();
1093 jmethodID mid = env->GetStaticMethodID(mActivityClass, "sendMessage", "(II)V");
1097 env->CallStaticVoidMethod(mActivityClass, mid, command, param);
1101 extern "C" void Android_JNI_ShowTextInput(SDL_Rect *inputRect)
1103 JNIEnv *env = Android_JNI_GetEnv();
1108 jmethodID mid = env->GetStaticMethodID(mActivityClass, "showTextInput", "(IIII)V");
1112 env->CallStaticVoidMethod( mActivityClass, mid,
1119 extern "C" void Android_JNI_HideTextInput()
1121 // has to match Activity constant
1122 const int COMMAND_TEXTEDIT_HIDE = 3;
1123 Android_JNI_SendMessage(COMMAND_TEXTEDIT_HIDE, 0);
1126 //////////////////////////////////////////////////////////////////////////////
1128 // Functions exposed to SDL applications in SDL_system.h
1131 extern "C" void *SDL_AndroidGetJNIEnv()
1133 return Android_JNI_GetEnv();
1136 extern "C" void *SDL_AndroidGetActivity()
1138 LocalReferenceHolder refs(__FUNCTION__);
1141 JNIEnv *env = Android_JNI_GetEnv();
1142 if (!refs.init(env)) {
1146 // return SDLActivity.getContext();
1147 mid = env->GetStaticMethodID(mActivityClass,
1148 "getContext","()Landroid/content/Context;");
1149 return env->CallStaticObjectMethod(mActivityClass, mid);
1152 extern "C" const char * SDL_AndroidGetInternalStoragePath()
1154 static char *s_AndroidInternalFilesPath = NULL;
1156 if (!s_AndroidInternalFilesPath) {
1157 LocalReferenceHolder refs(__FUNCTION__);
1164 JNIEnv *env = Android_JNI_GetEnv();
1165 if (!refs.init(env)) {
1169 // context = SDLActivity.getContext();
1170 mid = env->GetStaticMethodID(mActivityClass,
1171 "getContext","()Landroid/content/Context;");
1172 context = env->CallStaticObjectMethod(mActivityClass, mid);
1174 // fileObj = context.getFilesDir();
1175 mid = env->GetMethodID(env->GetObjectClass(context),
1176 "getFilesDir", "()Ljava/io/File;");
1177 fileObject = env->CallObjectMethod(context, mid);
1179 SDL_SetError("Couldn't get internal directory");
1183 // path = fileObject.getAbsolutePath();
1184 mid = env->GetMethodID(env->GetObjectClass(fileObject),
1185 "getAbsolutePath", "()Ljava/lang/String;");
1186 pathString = (jstring)env->CallObjectMethod(fileObject, mid);
1188 path = env->GetStringUTFChars(pathString, NULL);
1189 s_AndroidInternalFilesPath = SDL_strdup(path);
1190 env->ReleaseStringUTFChars(pathString, path);
1192 return s_AndroidInternalFilesPath;
1195 extern "C" int SDL_AndroidGetExternalStorageState()
1197 LocalReferenceHolder refs(__FUNCTION__);
1200 jstring stateString;
1204 JNIEnv *env = Android_JNI_GetEnv();
1205 if (!refs.init(env)) {
1209 cls = env->FindClass("android/os/Environment");
1210 mid = env->GetStaticMethodID(cls,
1211 "getExternalStorageState", "()Ljava/lang/String;");
1212 stateString = (jstring)env->CallStaticObjectMethod(cls, mid);
1214 state = env->GetStringUTFChars(stateString, NULL);
1216 // Print an info message so people debugging know the storage state
1217 __android_log_print(ANDROID_LOG_INFO, "SDL", "external storage state: %s", state);
1219 if (SDL_strcmp(state, "mounted") == 0) {
1220 stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ |
1221 SDL_ANDROID_EXTERNAL_STORAGE_WRITE;
1222 } else if (SDL_strcmp(state, "mounted_ro") == 0) {
1223 stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ;
1227 env->ReleaseStringUTFChars(stateString, state);
1232 extern "C" const char * SDL_AndroidGetExternalStoragePath()
1234 static char *s_AndroidExternalFilesPath = NULL;
1236 if (!s_AndroidExternalFilesPath) {
1237 LocalReferenceHolder refs(__FUNCTION__);
1244 JNIEnv *env = Android_JNI_GetEnv();
1245 if (!refs.init(env)) {
1249 // context = SDLActivity.getContext();
1250 mid = env->GetStaticMethodID(mActivityClass,
1251 "getContext","()Landroid/content/Context;");
1252 context = env->CallStaticObjectMethod(mActivityClass, mid);
1254 // fileObj = context.getExternalFilesDir();
1255 mid = env->GetMethodID(env->GetObjectClass(context),
1256 "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;");
1257 fileObject = env->CallObjectMethod(context, mid, NULL);
1259 SDL_SetError("Couldn't get external directory");
1263 // path = fileObject.getAbsolutePath();
1264 mid = env->GetMethodID(env->GetObjectClass(fileObject),
1265 "getAbsolutePath", "()Ljava/lang/String;");
1266 pathString = (jstring)env->CallObjectMethod(fileObject, mid);
1268 path = env->GetStringUTFChars(pathString, NULL);
1269 s_AndroidExternalFilesPath = SDL_strdup(path);
1270 env->ReleaseStringUTFChars(pathString, path);
1272 return s_AndroidExternalFilesPath;
1275 #endif /* __ANDROID__ */
1277 /* vi: set ts=4 sw=4 expandtab: */