Corrected internal documentation in source.
The parameter silent is no longer optional.
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2013 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"
32 #include "../../events/SDL_events_c.h"
33 #include "../../video/android/SDL_androidkeyboard.h"
34 #include "../../video/android/SDL_androidtouch.h"
35 #include "../../video/android/SDL_androidvideo.h"
37 #include <android/log.h>
39 #include <sys/types.h>
41 #define LOG_TAG "SDL_android"
42 //#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
43 //#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
44 #define LOGI(...) do {} while (false)
45 #define LOGE(...) do {} while (false)
47 /* Uncomment this to log messages entering and exiting methods in this file */
50 /* Implemented in audio/android/SDL_androidaudio.c */
51 extern void Android_RunAudioThread();
53 static void Android_JNI_ThreadDestroyed(void*);
55 /*******************************************************************************
56 This file links the Java side of Android with libsdl
57 *******************************************************************************/
59 #include <android/log.h>
63 /*******************************************************************************
65 *******************************************************************************/
66 static pthread_key_t mThreadKey;
67 static JavaVM* mJavaVM;
70 static jclass mActivityClass;
73 static jmethodID midCreateGLContext;
74 static jmethodID midFlipBuffers;
75 static jmethodID midAudioInit;
76 static jmethodID midAudioWriteShortBuffer;
77 static jmethodID midAudioWriteByteBuffer;
78 static jmethodID midAudioQuit;
80 // Accelerometer data storage
81 static float fLastAccelerometer[3];
82 static bool bHasNewData;
84 /*******************************************************************************
85 Functions called by JNI
86 *******************************************************************************/
89 jint JNI_OnLoad(JavaVM* vm, void* reserved)
93 LOGI("JNI_OnLoad called");
94 if ((*mJavaVM)->GetEnv(mJavaVM, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
95 LOGE("Failed to get the environment using GetEnv()");
99 * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread
100 * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this
102 if (pthread_key_create(&mThreadKey, Android_JNI_ThreadDestroyed)) {
103 __android_log_print(ANDROID_LOG_ERROR, "SDL", "Error initializing pthread key");
106 Android_JNI_SetupThread();
109 return JNI_VERSION_1_4;
112 // Called before SDL_main() to initialize JNI bindings
113 void SDL_Android_Init(JNIEnv* mEnv, jclass cls)
115 __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init()");
117 Android_JNI_SetupThread();
119 mActivityClass = (jclass)((*mEnv)->NewGlobalRef(mEnv, cls));
121 midCreateGLContext = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
122 "createGLContext","(II[I)Z");
123 midFlipBuffers = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
124 "flipBuffers","()V");
125 midAudioInit = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
126 "audioInit", "(IZZI)V");
127 midAudioWriteShortBuffer = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
128 "audioWriteShortBuffer", "([S)V");
129 midAudioWriteByteBuffer = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
130 "audioWriteByteBuffer", "([B)V");
131 midAudioQuit = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
136 if(!midCreateGLContext || !midFlipBuffers || !midAudioInit ||
137 !midAudioWriteShortBuffer || !midAudioWriteByteBuffer || !midAudioQuit) {
138 __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL: Couldn't locate Java callbacks, check that they're named and typed correctly");
140 __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init() finished!");
144 void Java_org_libsdl_app_SDLActivity_onNativeResize(
145 JNIEnv* env, jclass jcls,
146 jint width, jint height, jint format)
148 Android_SetScreenResolution(width, height, format);
152 void Java_org_libsdl_app_SDLActivity_onNativeKeyDown(
153 JNIEnv* env, jclass jcls, jint keycode)
155 Android_OnKeyDown(keycode);
159 void Java_org_libsdl_app_SDLActivity_onNativeKeyUp(
160 JNIEnv* env, jclass jcls, jint keycode)
162 Android_OnKeyUp(keycode);
166 void Java_org_libsdl_app_SDLActivity_onNativeTouch(
167 JNIEnv* env, jclass jcls,
168 jint touch_device_id_in, jint pointer_finger_id_in,
169 jint action, jfloat x, jfloat y, jfloat p)
171 Android_OnTouch(touch_device_id_in, pointer_finger_id_in, action, x, y, p);
175 void Java_org_libsdl_app_SDLActivity_onNativeAccel(
176 JNIEnv* env, jclass jcls,
177 jfloat x, jfloat y, jfloat z)
179 fLastAccelerometer[0] = x;
180 fLastAccelerometer[1] = y;
181 fLastAccelerometer[2] = z;
186 void Java_org_libsdl_app_SDLActivity_nativeLowMemory(
187 JNIEnv* env, jclass cls)
189 SDL_SendAppEvent(SDL_APP_LOWMEMORY);
193 void Java_org_libsdl_app_SDLActivity_nativeQuit(
194 JNIEnv* env, jclass cls)
196 // Inject a SDL_QUIT event
198 SDL_SendAppEvent(SDL_APP_TERMINATING);
202 void Java_org_libsdl_app_SDLActivity_nativePause(
203 JNIEnv* env, jclass cls)
205 if (Android_Window) {
206 /* Signal the pause semaphore so the event loop knows to pause and (optionally) block itself */
207 if (!SDL_SemValue(Android_PauseSem)) SDL_SemPost(Android_PauseSem);
208 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_LOST, 0, 0);
209 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_MINIMIZED, 0, 0);
212 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativePause()");
213 SDL_SendAppEvent(SDL_APP_WILLENTERBACKGROUND);
214 SDL_SendAppEvent(SDL_APP_DIDENTERBACKGROUND);
218 void Java_org_libsdl_app_SDLActivity_nativeResume(
219 JNIEnv* env, jclass cls)
221 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeResume()");
222 SDL_SendAppEvent(SDL_APP_WILLENTERFOREGROUND);
223 SDL_SendAppEvent(SDL_APP_DIDENTERFOREGROUND);
225 if (Android_Window) {
226 /* Signal the resume semaphore so the event loop knows to resume and restore the GL Context
227 * We can't restore the GL Context here because it needs to be done on the SDL main thread
228 * and this function will be called from the Java thread instead.
230 if (!SDL_SemValue(Android_ResumeSem)) SDL_SemPost(Android_ResumeSem);
231 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_GAINED, 0, 0);
232 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_RESTORED, 0, 0);
236 void Java_org_libsdl_app_SDLActivity_nativeRunAudioThread(
237 JNIEnv* env, jclass cls)
239 /* This is the audio thread, with a different environment */
240 Android_JNI_SetupThread();
242 Android_RunAudioThread();
245 void Java_org_libsdl_app_SDLInputConnection_nativeCommitText(
246 JNIEnv* env, jclass cls,
247 jstring text, jint newCursorPosition)
249 const char *utftext = (*env)->GetStringUTFChars(env, text, NULL);
251 SDL_SendKeyboardText(utftext);
253 (*env)->ReleaseStringUTFChars(env, text, utftext);
256 void Java_org_libsdl_app_SDLInputConnection_nativeSetComposingText(
257 JNIEnv* env, jclass cls,
258 jstring text, jint newCursorPosition)
260 const char *utftext = (*env)->GetStringUTFChars(env, text, NULL);
262 SDL_SendEditingText(utftext, 0, 0);
264 (*env)->ReleaseStringUTFChars(env, text, utftext);
269 /*******************************************************************************
270 Functions called by SDL into Java
271 *******************************************************************************/
273 static int s_active = 0;
274 struct LocalReferenceHolder
280 static struct LocalReferenceHolder LocalReferenceHolder_Setup(const char *func)
282 struct LocalReferenceHolder refholder;
283 refholder.m_env = NULL;
284 refholder.m_func = func;
286 SDL_Log("Entering function %s", func);
291 static SDL_bool LocalReferenceHolder_Init(struct LocalReferenceHolder *refholder, JNIEnv *env)
293 const int capacity = 16;
294 if ((*env)->PushLocalFrame(env, capacity) < 0) {
295 SDL_SetError("Failed to allocate enough JVM local references");
299 refholder->m_env = env;
303 static void LocalReferenceHolder_Cleanup(struct LocalReferenceHolder *refholder)
306 SDL_Log("Leaving function %s", refholder->m_func);
308 if (refholder->m_env) {
309 JNIEnv* env = refholder->m_env;
310 (*env)->PopLocalFrame(env, NULL);
315 static SDL_bool LocalReferenceHolder_IsActive()
321 SDL_bool Android_JNI_CreateContext(int majorVersion, int minorVersion,
322 int red, int green, int blue, int alpha,
323 int buffer, int depth, int stencil,
324 int buffers, int samples)
326 JNIEnv *env = Android_JNI_GetEnv();
330 EGL_GREEN_SIZE, green,
332 EGL_ALPHA_SIZE, alpha,
333 EGL_BUFFER_SIZE, buffer,
334 EGL_DEPTH_SIZE, depth,
335 EGL_STENCIL_SIZE, stencil,
336 EGL_SAMPLE_BUFFERS, buffers,
337 EGL_SAMPLES, samples,
338 EGL_RENDERABLE_TYPE, (majorVersion == 1 ? EGL_OPENGL_ES_BIT : EGL_OPENGL_ES2_BIT),
341 int len = SDL_arraysize(attribs);
345 array = (*env)->NewIntArray(env, len);
346 (*env)->SetIntArrayRegion(env, array, 0, len, attribs);
348 jboolean success = (*env)->CallStaticBooleanMethod(env, mActivityClass, midCreateGLContext, majorVersion, minorVersion, array);
350 (*env)->DeleteLocalRef(env, array);
352 return success ? SDL_TRUE : SDL_FALSE;
355 void Android_JNI_SwapWindow()
357 JNIEnv *mEnv = Android_JNI_GetEnv();
358 (*mEnv)->CallStaticVoidMethod(mEnv, mActivityClass, midFlipBuffers);
361 void Android_JNI_SetActivityTitle(const char *title)
364 JNIEnv *mEnv = Android_JNI_GetEnv();
365 mid = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,"setActivityTitle","(Ljava/lang/String;)Z");
367 jstring jtitle = (jstring)((*mEnv)->NewStringUTF(mEnv, title));
368 (*mEnv)->CallStaticBooleanMethod(mEnv, mActivityClass, mid, jtitle);
369 (*mEnv)->DeleteLocalRef(mEnv, jtitle);
373 SDL_bool Android_JNI_GetAccelerometerValues(float values[3])
376 SDL_bool retval = SDL_FALSE;
379 for (i = 0; i < 3; ++i) {
380 values[i] = fLastAccelerometer[i];
389 static void Android_JNI_ThreadDestroyed(void* value) {
390 /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */
391 JNIEnv *env = (JNIEnv*) value;
393 (*mJavaVM)->DetachCurrentThread(mJavaVM);
394 pthread_setspecific(mThreadKey, NULL);
398 JNIEnv* Android_JNI_GetEnv(void) {
399 /* From http://developer.android.com/guide/practices/jni.html
400 * All threads are Linux threads, scheduled by the kernel.
401 * They're usually started from managed code (using Thread.start), but they can also be created elsewhere and then
402 * attached to the JavaVM. For example, a thread started with pthread_create can be attached with the
403 * JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a thread is attached, it has no JNIEnv,
404 * and cannot make JNI calls.
405 * Attaching a natively-created thread causes a java.lang.Thread object to be constructed and added to the "main"
406 * ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread on an already-attached thread
408 * Note: You can call this function any number of times for the same thread, there's no harm in it
412 int status = (*mJavaVM)->AttachCurrentThread(mJavaVM, &env, NULL);
414 LOGE("failed to attach current thread");
421 int Android_JNI_SetupThread(void) {
422 /* From http://developer.android.com/guide/practices/jni.html
423 * Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward,
424 * in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be
425 * called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific
426 * to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.)
427 * Note: The destructor is not called unless the stored value is != NULL
428 * Note: You can call this function any number of times for the same thread, there's no harm in it
429 * (except for some lost CPU cycles)
431 JNIEnv *env = Android_JNI_GetEnv();
432 pthread_setspecific(mThreadKey, (void*) env);
439 static jboolean audioBuffer16Bit = JNI_FALSE;
440 static jboolean audioBufferStereo = JNI_FALSE;
441 static jobject audioBuffer = NULL;
442 static void* audioBufferPinned = NULL;
444 int Android_JNI_OpenAudioDevice(int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
446 int audioBufferFrames;
448 JNIEnv *env = Android_JNI_GetEnv();
451 LOGE("callback_handler: failed to attach current thread");
453 Android_JNI_SetupThread();
455 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device");
456 audioBuffer16Bit = is16Bit;
457 audioBufferStereo = channelCount > 1;
459 (*env)->CallStaticVoidMethod(env, mActivityClass, midAudioInit, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames);
461 /* Allocating the audio buffer from the Java side and passing it as the return value for audioInit no longer works on
462 * Android >= 4.2 due to a "stale global reference" error. So now we allocate this buffer directly from this side. */
465 jshortArray audioBufferLocal = (*env)->NewShortArray(env, desiredBufferFrames * (audioBufferStereo ? 2 : 1));
466 if (audioBufferLocal) {
467 audioBuffer = (*env)->NewGlobalRef(env, audioBufferLocal);
468 (*env)->DeleteLocalRef(env, audioBufferLocal);
472 jbyteArray audioBufferLocal = (*env)->NewByteArray(env, desiredBufferFrames * (audioBufferStereo ? 2 : 1));
473 if (audioBufferLocal) {
474 audioBuffer = (*env)->NewGlobalRef(env, audioBufferLocal);
475 (*env)->DeleteLocalRef(env, audioBufferLocal);
479 if (audioBuffer == NULL) {
480 __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: could not allocate an audio buffer!");
484 jboolean isCopy = JNI_FALSE;
485 if (audioBuffer16Bit) {
486 audioBufferPinned = (*env)->GetShortArrayElements(env, (jshortArray)audioBuffer, &isCopy);
487 audioBufferFrames = (*env)->GetArrayLength(env, (jshortArray)audioBuffer);
489 audioBufferPinned = (*env)->GetByteArrayElements(env, (jbyteArray)audioBuffer, &isCopy);
490 audioBufferFrames = (*env)->GetArrayLength(env, (jbyteArray)audioBuffer);
492 if (audioBufferStereo) {
493 audioBufferFrames /= 2;
496 return audioBufferFrames;
499 void * Android_JNI_GetAudioBuffer()
501 return audioBufferPinned;
504 void Android_JNI_WriteAudioBuffer()
506 JNIEnv *mAudioEnv = Android_JNI_GetEnv();
508 if (audioBuffer16Bit) {
509 (*mAudioEnv)->ReleaseShortArrayElements(mAudioEnv, (jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
510 (*mAudioEnv)->CallStaticVoidMethod(mAudioEnv, mActivityClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
512 (*mAudioEnv)->ReleaseByteArrayElements(mAudioEnv, (jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT);
513 (*mAudioEnv)->CallStaticVoidMethod(mAudioEnv, mActivityClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer);
516 /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
519 void Android_JNI_CloseAudioDevice()
521 JNIEnv *env = Android_JNI_GetEnv();
523 (*env)->CallStaticVoidMethod(env, mActivityClass, midAudioQuit);
526 (*env)->DeleteGlobalRef(env, audioBuffer);
528 audioBufferPinned = NULL;
532 // Test for an exception and call SDL_SetError with its detail if one occurs
533 // If the parameter silent is truthy then SDL_SetError() will not be called.
534 static bool Android_JNI_ExceptionOccurred(bool silent)
536 SDL_assert(LocalReferenceHolder_IsActive());
537 JNIEnv *mEnv = Android_JNI_GetEnv();
539 jthrowable exception = (*mEnv)->ExceptionOccurred(mEnv);
540 if (exception != NULL) {
543 // Until this happens most JNI operations have undefined behaviour
544 (*mEnv)->ExceptionClear(mEnv);
547 jclass exceptionClass = (*mEnv)->GetObjectClass(mEnv, exception);
548 jclass classClass = (*mEnv)->FindClass(mEnv, "java/lang/Class");
550 mid = (*mEnv)->GetMethodID(mEnv, classClass, "getName", "()Ljava/lang/String;");
551 jstring exceptionName = (jstring)(*mEnv)->CallObjectMethod(mEnv, exceptionClass, mid);
552 const char* exceptionNameUTF8 = (*mEnv)->GetStringUTFChars(mEnv, exceptionName, 0);
554 mid = (*mEnv)->GetMethodID(mEnv, exceptionClass, "getMessage", "()Ljava/lang/String;");
555 jstring exceptionMessage = (jstring)(*mEnv)->CallObjectMethod(mEnv, exception, mid);
557 if (exceptionMessage != NULL) {
558 const char* exceptionMessageUTF8 = (*mEnv)->GetStringUTFChars(mEnv, exceptionMessage, 0);
559 SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
560 (*mEnv)->ReleaseStringUTFChars(mEnv, exceptionMessage, exceptionMessageUTF8);
562 SDL_SetError("%s", exceptionNameUTF8);
565 (*mEnv)->ReleaseStringUTFChars(mEnv, exceptionName, exceptionNameUTF8);
574 static int Internal_Android_JNI_FileOpen(SDL_RWops* ctx)
576 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
582 jobject assetManager;
585 jobject readableByteChannel;
586 jstring fileNameJString;
591 JNIEnv *mEnv = Android_JNI_GetEnv();
592 if (!LocalReferenceHolder_Init(&refs, mEnv)) {
596 fileNameJString = (jstring)ctx->hidden.androidio.fileNameRef;
597 ctx->hidden.androidio.position = 0;
599 // context = SDLActivity.getContext();
600 mid = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
601 "getContext","()Landroid/content/Context;");
602 context = (*mEnv)->CallStaticObjectMethod(mEnv, mActivityClass, mid);
605 // assetManager = context.getAssets();
606 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, context),
607 "getAssets", "()Landroid/content/res/AssetManager;");
608 assetManager = (*mEnv)->CallObjectMethod(mEnv, context, mid);
610 /* First let's try opening the file to obtain an AssetFileDescriptor.
611 * This method reads the files directly from the APKs using standard *nix calls
613 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, assetManager), "openFd", "(Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;");
614 inputStream = (*mEnv)->CallObjectMethod(mEnv, assetManager, mid, fileNameJString);
615 if (Android_JNI_ExceptionOccurred(true)) {
619 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getStartOffset", "()J");
620 ctx->hidden.androidio.offset = (*mEnv)->CallLongMethod(mEnv, inputStream, mid);
621 if (Android_JNI_ExceptionOccurred(true)) {
625 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getDeclaredLength", "()J");
626 ctx->hidden.androidio.size = (*mEnv)->CallLongMethod(mEnv, inputStream, mid);
627 if (Android_JNI_ExceptionOccurred(true)) {
631 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getFileDescriptor", "()Ljava/io/FileDescriptor;");
632 fd = (*mEnv)->CallObjectMethod(mEnv, inputStream, mid);
633 fdCls = (*mEnv)->GetObjectClass(mEnv, fd);
634 descriptor = (*mEnv)->GetFieldID(mEnv, fdCls, "descriptor", "I");
635 ctx->hidden.androidio.fd = (*mEnv)->GetIntField(mEnv, fd, descriptor);
636 ctx->hidden.androidio.assetFileDescriptorRef = (*mEnv)->NewGlobalRef(mEnv, inputStream);
638 // Seek to the correct offset in the file.
639 lseek(ctx->hidden.androidio.fd, (off_t)ctx->hidden.androidio.offset, SEEK_SET);
643 // Disabled log message because of spam on the Nexus 7
644 //__android_log_print(ANDROID_LOG_DEBUG, "SDL", "Falling back to legacy InputStream method for opening file");
646 /* Try the old method using InputStream */
647 ctx->hidden.androidio.assetFileDescriptorRef = NULL;
649 // inputStream = assetManager.open(<filename>);
650 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, assetManager),
651 "open", "(Ljava/lang/String;I)Ljava/io/InputStream;");
652 inputStream = (*mEnv)->CallObjectMethod(mEnv, assetManager, mid, fileNameJString, 1 /*ACCESS_RANDOM*/);
653 if (Android_JNI_ExceptionOccurred(false)) {
657 ctx->hidden.androidio.inputStreamRef = (*mEnv)->NewGlobalRef(mEnv, inputStream);
659 // Despite all the visible documentation on [Asset]InputStream claiming
660 // that the .available() method is not guaranteed to return the entire file
661 // size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
662 // android/apis/content/ReadAsset.java imply that Android's
663 // AssetInputStream.available() /will/ always return the total file size
665 // size = inputStream.available();
666 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream),
668 ctx->hidden.androidio.size = (long)(*mEnv)->CallIntMethod(mEnv, inputStream, mid);
669 if (Android_JNI_ExceptionOccurred(false)) {
673 // readableByteChannel = Channels.newChannel(inputStream);
674 channels = (*mEnv)->FindClass(mEnv, "java/nio/channels/Channels");
675 mid = (*mEnv)->GetStaticMethodID(mEnv, channels,
677 "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
678 readableByteChannel = (*mEnv)->CallStaticObjectMethod(
679 mEnv, channels, mid, inputStream);
680 if (Android_JNI_ExceptionOccurred(false)) {
684 ctx->hidden.androidio.readableByteChannelRef =
685 (*mEnv)->NewGlobalRef(mEnv, readableByteChannel);
687 // Store .read id for reading purposes
688 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, readableByteChannel),
689 "read", "(Ljava/nio/ByteBuffer;)I");
690 ctx->hidden.androidio.readMethod = mid;
697 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.fileNameRef);
699 if(ctx->hidden.androidio.inputStreamRef != NULL) {
700 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.inputStreamRef);
703 if(ctx->hidden.androidio.readableByteChannelRef != NULL) {
704 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.readableByteChannelRef);
707 if(ctx->hidden.androidio.assetFileDescriptorRef != NULL) {
708 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.assetFileDescriptorRef);
713 LocalReferenceHolder_Cleanup(&refs);
717 int Android_JNI_FileOpen(SDL_RWops* ctx,
718 const char* fileName, const char* mode)
720 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
721 JNIEnv *mEnv = Android_JNI_GetEnv();
724 if (!LocalReferenceHolder_Init(&refs, mEnv)) {
725 LocalReferenceHolder_Cleanup(&refs);
730 LocalReferenceHolder_Cleanup(&refs);
734 jstring fileNameJString = (*mEnv)->NewStringUTF(mEnv, fileName);
735 ctx->hidden.androidio.fileNameRef = (*mEnv)->NewGlobalRef(mEnv, fileNameJString);
736 ctx->hidden.androidio.inputStreamRef = NULL;
737 ctx->hidden.androidio.readableByteChannelRef = NULL;
738 ctx->hidden.androidio.readMethod = NULL;
739 ctx->hidden.androidio.assetFileDescriptorRef = NULL;
741 retval = Internal_Android_JNI_FileOpen(ctx);
742 LocalReferenceHolder_Cleanup(&refs);
746 size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
747 size_t size, size_t maxnum)
749 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
751 if (ctx->hidden.androidio.assetFileDescriptorRef) {
752 size_t bytesMax = size * maxnum;
753 if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && ctx->hidden.androidio.position + bytesMax > ctx->hidden.androidio.size) {
754 bytesMax = ctx->hidden.androidio.size - ctx->hidden.androidio.position;
756 size_t result = read(ctx->hidden.androidio.fd, buffer, bytesMax );
758 ctx->hidden.androidio.position += result;
759 LocalReferenceHolder_Cleanup(&refs);
760 return result / size;
762 LocalReferenceHolder_Cleanup(&refs);
765 jlong bytesRemaining = (jlong) (size * maxnum);
766 jlong bytesMax = (jlong) (ctx->hidden.androidio.size - ctx->hidden.androidio.position);
769 /* Don't read more bytes than those that remain in the file, otherwise we get an exception */
770 if (bytesRemaining > bytesMax) bytesRemaining = bytesMax;
772 JNIEnv *mEnv = Android_JNI_GetEnv();
773 if (!LocalReferenceHolder_Init(&refs, mEnv)) {
774 LocalReferenceHolder_Cleanup(&refs);
778 jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannelRef;
779 jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
780 jobject byteBuffer = (*mEnv)->NewDirectByteBuffer(mEnv, buffer, bytesRemaining);
782 while (bytesRemaining > 0) {
783 // result = readableByteChannel.read(...);
784 int result = (*mEnv)->CallIntMethod(mEnv, readableByteChannel, readMethod, byteBuffer);
786 if (Android_JNI_ExceptionOccurred(false)) {
787 LocalReferenceHolder_Cleanup(&refs);
795 bytesRemaining -= result;
797 ctx->hidden.androidio.position += result;
799 LocalReferenceHolder_Cleanup(&refs);
800 return bytesRead / size;
804 size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
805 size_t size, size_t num)
807 SDL_SetError("Cannot write to Android package filesystem");
811 static int Internal_Android_JNI_FileClose(SDL_RWops* ctx, bool release)
813 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
816 JNIEnv *mEnv = Android_JNI_GetEnv();
818 if (!LocalReferenceHolder_Init(&refs, mEnv)) {
819 LocalReferenceHolder_Cleanup(&refs);
820 return SDL_SetError("Failed to allocate enough JVM local references");
825 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.fileNameRef);
828 if (ctx->hidden.androidio.assetFileDescriptorRef) {
829 jobject inputStream = (jobject)ctx->hidden.androidio.assetFileDescriptorRef;
830 jmethodID mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream),
832 (*mEnv)->CallVoidMethod(mEnv, inputStream, mid);
833 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.assetFileDescriptorRef);
834 if (Android_JNI_ExceptionOccurred(false)) {
839 jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
841 // inputStream.close();
842 jmethodID mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream),
844 (*mEnv)->CallVoidMethod(mEnv, inputStream, mid);
845 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.inputStreamRef);
846 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.readableByteChannelRef);
847 if (Android_JNI_ExceptionOccurred(false)) {
857 LocalReferenceHolder_Cleanup(&refs);
862 Sint64 Android_JNI_FileSize(SDL_RWops* ctx)
864 return ctx->hidden.androidio.size;
867 Sint64 Android_JNI_FileSeek(SDL_RWops* ctx, Sint64 offset, int whence)
869 if (ctx->hidden.androidio.assetFileDescriptorRef) {
872 if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
873 offset += ctx->hidden.androidio.offset;
876 offset += ctx->hidden.androidio.position;
877 if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
878 offset += ctx->hidden.androidio.offset;
881 offset = ctx->hidden.androidio.offset + ctx->hidden.androidio.size + offset;
884 return SDL_SetError("Unknown value for 'whence'");
888 off_t ret = lseek(ctx->hidden.androidio.fd, (off_t)offset, SEEK_SET);
889 if (ret == -1) return -1;
890 ctx->hidden.androidio.position = ret - ctx->hidden.androidio.offset;
896 newPosition = offset;
899 newPosition = ctx->hidden.androidio.position + offset;
902 newPosition = ctx->hidden.androidio.size + offset;
905 return SDL_SetError("Unknown value for 'whence'");
908 /* Validate the new position */
909 if (newPosition < 0) {
910 return SDL_Error(SDL_EFSEEK);
912 if (newPosition > ctx->hidden.androidio.size) {
913 newPosition = ctx->hidden.androidio.size;
916 Sint64 movement = newPosition - ctx->hidden.androidio.position;
918 unsigned char buffer[4096];
920 // The easy case where we're seeking forwards
921 while (movement > 0) {
922 Sint64 amount = sizeof (buffer);
923 if (amount > movement) {
926 size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount);
928 // Failed to read/skip the required amount, so fail
935 } else if (movement < 0) {
936 // We can't seek backwards so we have to reopen the file and seek
937 // forwards which obviously isn't very efficient
938 Internal_Android_JNI_FileClose(ctx, false);
939 Internal_Android_JNI_FileOpen(ctx);
940 Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
944 return ctx->hidden.androidio.position;
948 int Android_JNI_FileClose(SDL_RWops* ctx)
950 return Internal_Android_JNI_FileClose(ctx, true);
953 // returns a new global reference which needs to be released later
954 static jobject Android_JNI_GetSystemServiceObject(const char* name)
956 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
957 JNIEnv* env = Android_JNI_GetEnv();
958 jobject retval = NULL;
960 if (!LocalReferenceHolder_Init(&refs, env)) {
961 LocalReferenceHolder_Cleanup(&refs);
965 jstring service = (*env)->NewStringUTF(env, name);
969 mid = (*env)->GetStaticMethodID(env, mActivityClass, "getContext", "()Landroid/content/Context;");
970 jobject context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
972 mid = (*env)->GetMethodID(env, mActivityClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
973 jobject manager = (*env)->CallObjectMethod(env, context, mid, service);
975 (*env)->DeleteLocalRef(env, service);
977 retval = manager ? (*env)->NewGlobalRef(env, manager) : NULL;
978 LocalReferenceHolder_Cleanup(&refs);
982 #define SETUP_CLIPBOARD(error) \
983 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); \
984 JNIEnv* env = Android_JNI_GetEnv(); \
985 if (!LocalReferenceHolder_Init(&refs, env)) { \
986 LocalReferenceHolder_Cleanup(&refs); \
989 jobject clipboard = Android_JNI_GetSystemServiceObject("clipboard"); \
991 LocalReferenceHolder_Cleanup(&refs); \
995 #define CLEANUP_CLIPBOARD() \
996 LocalReferenceHolder_Cleanup(&refs);
998 int Android_JNI_SetClipboardText(const char* text)
1002 jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, clipboard), "setText", "(Ljava/lang/CharSequence;)V");
1003 jstring string = (*env)->NewStringUTF(env, text);
1004 (*env)->CallVoidMethod(env, clipboard, mid, string);
1005 (*env)->DeleteGlobalRef(env, clipboard);
1006 (*env)->DeleteLocalRef(env, string);
1008 CLEANUP_CLIPBOARD();
1013 char* Android_JNI_GetClipboardText()
1015 SETUP_CLIPBOARD(SDL_strdup(""))
1017 jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, clipboard), "getText", "()Ljava/lang/CharSequence;");
1018 jobject sequence = (*env)->CallObjectMethod(env, clipboard, mid);
1019 (*env)->DeleteGlobalRef(env, clipboard);
1021 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, sequence), "toString", "()Ljava/lang/String;");
1022 jstring string = (jstring)((*env)->CallObjectMethod(env, sequence, mid));
1023 const char* utf = (*env)->GetStringUTFChars(env, string, 0);
1025 char* text = SDL_strdup(utf);
1026 (*env)->ReleaseStringUTFChars(env, string, utf);
1028 CLEANUP_CLIPBOARD();
1034 CLEANUP_CLIPBOARD();
1036 return SDL_strdup("");
1039 SDL_bool Android_JNI_HasClipboardText()
1041 SETUP_CLIPBOARD(SDL_FALSE)
1043 jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, clipboard), "hasText", "()Z");
1044 jboolean has = (*env)->CallBooleanMethod(env, clipboard, mid);
1045 (*env)->DeleteGlobalRef(env, clipboard);
1047 CLEANUP_CLIPBOARD();
1049 return has ? SDL_TRUE : SDL_FALSE;
1053 // returns 0 on success or -1 on error (others undefined then)
1054 // returns truthy or falsy value in plugged, charged and battery
1055 // returns the value in seconds and percent or -1 if not available
1056 int Android_JNI_GetPowerInfo(int* plugged, int* charged, int* battery, int* seconds, int* percent)
1058 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
1059 JNIEnv* env = Android_JNI_GetEnv();
1060 if (!LocalReferenceHolder_Init(&refs, env)) {
1061 LocalReferenceHolder_Cleanup(&refs);
1067 mid = (*env)->GetStaticMethodID(env, mActivityClass, "getContext", "()Landroid/content/Context;");
1068 jobject context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
1070 jstring action = (*env)->NewStringUTF(env, "android.intent.action.BATTERY_CHANGED");
1072 jclass cls = (*env)->FindClass(env, "android/content/IntentFilter");
1074 mid = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/lang/String;)V");
1075 jobject filter = (*env)->NewObject(env, cls, mid, action);
1077 (*env)->DeleteLocalRef(env, action);
1079 mid = (*env)->GetMethodID(env, mActivityClass, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;");
1080 jobject intent = (*env)->CallObjectMethod(env, context, mid, NULL, filter);
1082 (*env)->DeleteLocalRef(env, filter);
1084 cls = (*env)->GetObjectClass(env, intent);
1087 jmethodID imid = (*env)->GetMethodID(env, cls, "getIntExtra", "(Ljava/lang/String;I)I");
1089 #define GET_INT_EXTRA(var, key) \
1090 iname = (*env)->NewStringUTF(env, key); \
1091 int var = (*env)->CallIntMethod(env, intent, imid, iname, -1); \
1092 (*env)->DeleteLocalRef(env, iname);
1095 jmethodID bmid = (*env)->GetMethodID(env, cls, "getBooleanExtra", "(Ljava/lang/String;Z)Z");
1097 #define GET_BOOL_EXTRA(var, key) \
1098 bname = (*env)->NewStringUTF(env, key); \
1099 int var = (*env)->CallBooleanMethod(env, intent, bmid, bname, JNI_FALSE); \
1100 (*env)->DeleteLocalRef(env, bname);
1103 GET_INT_EXTRA(plug, "plugged") // == BatteryManager.EXTRA_PLUGGED (API 5)
1105 LocalReferenceHolder_Cleanup(&refs);
1108 // 1 == BatteryManager.BATTERY_PLUGGED_AC
1109 // 2 == BatteryManager.BATTERY_PLUGGED_USB
1110 *plugged = (0 < plug) ? 1 : 0;
1114 GET_INT_EXTRA(status, "status") // == BatteryManager.EXTRA_STATUS (API 5)
1116 LocalReferenceHolder_Cleanup(&refs);
1119 // 5 == BatteryManager.BATTERY_STATUS_FULL
1120 *charged = (status == 5) ? 1 : 0;
1124 GET_BOOL_EXTRA(present, "present") // == BatteryManager.EXTRA_PRESENT (API 5)
1125 *battery = present ? 1 : 0;
1129 *seconds = -1; // not possible
1133 GET_INT_EXTRA(level, "level") // == BatteryManager.EXTRA_LEVEL (API 5)
1134 GET_INT_EXTRA(scale, "scale") // == BatteryManager.EXTRA_SCALE (API 5)
1135 if ((level == -1) || (scale == -1)) {
1136 LocalReferenceHolder_Cleanup(&refs);
1139 *percent = level * 100 / scale;
1142 (*env)->DeleteLocalRef(env, intent);
1144 LocalReferenceHolder_Cleanup(&refs);
1148 // sends message to be handled on the UI event dispatch thread
1149 int Android_JNI_SendMessage(int command, int param)
1151 JNIEnv *env = Android_JNI_GetEnv();
1155 jmethodID mid = (*env)->GetStaticMethodID(env, mActivityClass, "sendMessage", "(II)Z");
1159 jboolean success = (*env)->CallStaticBooleanMethod(env, mActivityClass, mid, command, param);
1160 return success ? 0 : -1;
1163 void Android_JNI_ShowTextInput(SDL_Rect *inputRect)
1165 JNIEnv *env = Android_JNI_GetEnv();
1170 jmethodID mid = (*env)->GetStaticMethodID(env, mActivityClass, "showTextInput", "(IIII)Z");
1174 (*env)->CallStaticBooleanMethod(env, mActivityClass, mid,
1181 void Android_JNI_HideTextInput()
1183 // has to match Activity constant
1184 const int COMMAND_TEXTEDIT_HIDE = 3;
1185 Android_JNI_SendMessage(COMMAND_TEXTEDIT_HIDE, 0);
1188 //////////////////////////////////////////////////////////////////////////////
1190 // Functions exposed to SDL applications in SDL_system.h
1193 void *SDL_AndroidGetJNIEnv()
1195 return Android_JNI_GetEnv();
1200 void *SDL_AndroidGetActivity()
1202 /* See SDL_system.h for caveats on using this function. */
1206 JNIEnv *env = Android_JNI_GetEnv();
1211 // return SDLActivity.getContext();
1212 mid = (*env)->GetStaticMethodID(env, mActivityClass,
1213 "getContext","()Landroid/content/Context;");
1214 return (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
1217 const char * SDL_AndroidGetInternalStoragePath()
1219 static char *s_AndroidInternalFilesPath = NULL;
1221 if (!s_AndroidInternalFilesPath) {
1222 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
1229 JNIEnv *env = Android_JNI_GetEnv();
1230 if (!LocalReferenceHolder_Init(&refs, env)) {
1231 LocalReferenceHolder_Cleanup(&refs);
1235 // context = SDLActivity.getContext();
1236 mid = (*env)->GetStaticMethodID(env, mActivityClass,
1237 "getContext","()Landroid/content/Context;");
1238 context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
1240 // fileObj = context.getFilesDir();
1241 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
1242 "getFilesDir", "()Ljava/io/File;");
1243 fileObject = (*env)->CallObjectMethod(env, context, mid);
1245 SDL_SetError("Couldn't get internal directory");
1246 LocalReferenceHolder_Cleanup(&refs);
1250 // path = fileObject.getAbsolutePath();
1251 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject),
1252 "getAbsolutePath", "()Ljava/lang/String;");
1253 pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid);
1255 path = (*env)->GetStringUTFChars(env, pathString, NULL);
1256 s_AndroidInternalFilesPath = SDL_strdup(path);
1257 (*env)->ReleaseStringUTFChars(env, pathString, path);
1259 LocalReferenceHolder_Cleanup(&refs);
1261 return s_AndroidInternalFilesPath;
1264 int SDL_AndroidGetExternalStorageState()
1266 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
1269 jstring stateString;
1273 JNIEnv *env = Android_JNI_GetEnv();
1274 if (!LocalReferenceHolder_Init(&refs, env)) {
1275 LocalReferenceHolder_Cleanup(&refs);
1279 cls = (*env)->FindClass(env, "android/os/Environment");
1280 mid = (*env)->GetStaticMethodID(env, cls,
1281 "getExternalStorageState", "()Ljava/lang/String;");
1282 stateString = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid);
1284 state = (*env)->GetStringUTFChars(env, stateString, NULL);
1286 // Print an info message so people debugging know the storage state
1287 __android_log_print(ANDROID_LOG_INFO, "SDL", "external storage state: %s", state);
1289 if (SDL_strcmp(state, "mounted") == 0) {
1290 stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ |
1291 SDL_ANDROID_EXTERNAL_STORAGE_WRITE;
1292 } else if (SDL_strcmp(state, "mounted_ro") == 0) {
1293 stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ;
1297 (*env)->ReleaseStringUTFChars(env, stateString, state);
1299 LocalReferenceHolder_Cleanup(&refs);
1303 const char * SDL_AndroidGetExternalStoragePath()
1305 static char *s_AndroidExternalFilesPath = NULL;
1307 if (!s_AndroidExternalFilesPath) {
1308 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
1315 JNIEnv *env = Android_JNI_GetEnv();
1316 if (!LocalReferenceHolder_Init(&refs, env)) {
1317 LocalReferenceHolder_Cleanup(&refs);
1321 // context = SDLActivity.getContext();
1322 mid = (*env)->GetStaticMethodID(env, mActivityClass,
1323 "getContext","()Landroid/content/Context;");
1324 context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
1326 // fileObj = context.getExternalFilesDir();
1327 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
1328 "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;");
1329 fileObject = (*env)->CallObjectMethod(env, context, mid, NULL);
1331 SDL_SetError("Couldn't get external directory");
1332 LocalReferenceHolder_Cleanup(&refs);
1336 // path = fileObject.getAbsolutePath();
1337 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject),
1338 "getAbsolutePath", "()Ljava/lang/String;");
1339 pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid);
1341 path = (*env)->GetStringUTFChars(env, pathString, NULL);
1342 s_AndroidExternalFilesPath = SDL_strdup(path);
1343 (*env)->ReleaseStringUTFChars(env, pathString, path);
1345 LocalReferenceHolder_Cleanup(&refs);
1347 return s_AndroidExternalFilesPath;
1350 #endif /* __ANDROID__ */
1352 /* vi: set ts=4 sw=4 expandtab: */