Android: Fixed up drop events for new interface.
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2016 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_internal.h"
22 #include "SDL_stdinc.h"
23 #include "SDL_assert.h"
24 #include "SDL_hints.h"
29 #include "SDL_system.h"
30 #include "SDL_android.h"
33 #include "../../events/SDL_events_c.h"
34 #include "../../video/android/SDL_androidkeyboard.h"
35 #include "../../video/android/SDL_androidmouse.h"
36 #include "../../video/android/SDL_androidtouch.h"
37 #include "../../video/android/SDL_androidvideo.h"
38 #include "../../video/android/SDL_androidwindow.h"
39 #include "../../joystick/android/SDL_sysjoystick_c.h"
41 #include <android/log.h>
43 #include <sys/types.h>
45 #define LOG_TAG "SDL_android"
46 /* #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) */
47 /* #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) */
48 #define LOGI(...) do {} while (0)
49 #define LOGE(...) do {} while (0)
51 /* Uncomment this to log messages entering and exiting methods in this file */
52 /* #define DEBUG_JNI */
54 static void Android_JNI_ThreadDestroyed(void*);
56 /*******************************************************************************
57 This file links the Java side of Android with libsdl
58 *******************************************************************************/
60 #include <android/log.h>
63 /*******************************************************************************
65 *******************************************************************************/
66 static pthread_key_t mThreadKey;
67 static JavaVM* mJavaVM;
70 static jclass mActivityClass;
72 /* method signatures */
73 static jmethodID midGetNativeSurface;
74 static jmethodID midAudioInit;
75 static jmethodID midAudioWriteShortBuffer;
76 static jmethodID midAudioWriteByteBuffer;
77 static jmethodID midAudioQuit;
78 static jmethodID midPollInputDevices;
80 /* Accelerometer data storage */
81 static float fLastAccelerometer[3];
82 static SDL_bool bHasNewData;
84 /*******************************************************************************
85 Functions called by JNI
86 *******************************************************************************/
89 JNIEXPORT jint JNICALL 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) != 0) {
103 __android_log_print(ANDROID_LOG_ERROR, "SDL", "Error initializing pthread key");
105 Android_JNI_SetupThread();
107 return JNI_VERSION_1_4;
110 /* Called before SDL_main() to initialize JNI bindings */
111 JNIEXPORT void JNICALL SDL_Android_Init(JNIEnv* mEnv, jclass cls)
113 __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init()");
115 Android_JNI_SetupThread();
117 mActivityClass = (jclass)((*mEnv)->NewGlobalRef(mEnv, cls));
119 midGetNativeSurface = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
120 "getNativeSurface","()Landroid/view/Surface;");
121 midAudioInit = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
122 "audioInit", "(IZZI)I");
123 midAudioWriteShortBuffer = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
124 "audioWriteShortBuffer", "([S)V");
125 midAudioWriteByteBuffer = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
126 "audioWriteByteBuffer", "([B)V");
127 midAudioQuit = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
129 midPollInputDevices = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
130 "pollInputDevices", "()V");
132 bHasNewData = SDL_FALSE;
134 if (!midGetNativeSurface || !midAudioInit ||
135 !midAudioWriteShortBuffer || !midAudioWriteByteBuffer || !midAudioQuit || !midPollInputDevices) {
136 __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL: Couldn't locate Java callbacks, check that they're named and typed correctly");
138 __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init() finished!");
142 void Java_org_libsdl_app_SDLActivity_onNativeDropFile(
143 JNIEnv* env, jclass jcls,
146 const char *path = (*env)->GetStringUTFChars(env, filename, NULL);
147 SDL_SendDropFile(NULL, path);
148 (*env)->ReleaseStringUTFChars(env, filename, path);
149 SDL_SendDropComplete(NULL);
153 JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_onNativeResize(
154 JNIEnv* env, jclass jcls,
155 jint width, jint height, jint format, jfloat rate)
157 Android_SetScreenResolution(width, height, format, rate);
161 JNIEXPORT jint JNICALL Java_org_libsdl_app_SDLActivity_onNativePadDown(
162 JNIEnv* env, jclass jcls,
163 jint device_id, jint keycode)
165 return Android_OnPadDown(device_id, keycode);
169 JNIEXPORT jint JNICALL Java_org_libsdl_app_SDLActivity_onNativePadUp(
170 JNIEnv* env, jclass jcls,
171 jint device_id, jint keycode)
173 return Android_OnPadUp(device_id, keycode);
177 JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_onNativeJoy(
178 JNIEnv* env, jclass jcls,
179 jint device_id, jint axis, jfloat value)
181 Android_OnJoy(device_id, axis, value);
185 JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_onNativeHat(
186 JNIEnv* env, jclass jcls,
187 jint device_id, jint hat_id, jint x, jint y)
189 Android_OnHat(device_id, hat_id, x, y);
193 JNIEXPORT jint JNICALL Java_org_libsdl_app_SDLActivity_nativeAddJoystick(
194 JNIEnv* env, jclass jcls,
195 jint device_id, jstring device_name, jint is_accelerometer,
196 jint nbuttons, jint naxes, jint nhats, jint nballs)
199 const char *name = (*env)->GetStringUTFChars(env, device_name, NULL);
201 retval = Android_AddJoystick(device_id, name, (SDL_bool) is_accelerometer, nbuttons, naxes, nhats, nballs);
203 (*env)->ReleaseStringUTFChars(env, device_name, name);
208 JNIEXPORT jint JNICALL Java_org_libsdl_app_SDLActivity_nativeRemoveJoystick(
209 JNIEnv* env, jclass jcls, jint device_id)
211 return Android_RemoveJoystick(device_id);
215 /* Surface Created */
216 JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_onNativeSurfaceChanged(JNIEnv* env, jclass jcls)
218 SDL_WindowData *data;
219 SDL_VideoDevice *_this;
221 if (Android_Window == NULL || Android_Window->driverdata == NULL ) {
225 _this = SDL_GetVideoDevice();
226 data = (SDL_WindowData *) Android_Window->driverdata;
228 /* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */
229 if (data->egl_surface == EGL_NO_SURFACE) {
230 if(data->native_window) {
231 ANativeWindow_release(data->native_window);
233 data->native_window = Android_JNI_GetNativeWindow();
234 data->egl_surface = SDL_EGL_CreateSurface(_this, (NativeWindowType) data->native_window);
237 /* GL Context handling is done in the event loop because this function is run from the Java thread */
241 /* Surface Destroyed */
242 JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_onNativeSurfaceDestroyed(JNIEnv* env, jclass jcls)
244 /* We have to clear the current context and destroy the egl surface here
245 * Otherwise there's BAD_NATIVE_WINDOW errors coming from eglCreateWindowSurface on resume
246 * Ref: http://stackoverflow.com/questions/8762589/eglcreatewindowsurface-on-ics-and-switching-from-2d-to-3d
248 SDL_WindowData *data;
249 SDL_VideoDevice *_this;
251 if (Android_Window == NULL || Android_Window->driverdata == NULL ) {
255 _this = SDL_GetVideoDevice();
256 data = (SDL_WindowData *) Android_Window->driverdata;
258 if (data->egl_surface != EGL_NO_SURFACE) {
259 SDL_EGL_MakeCurrent(_this, NULL, NULL);
260 SDL_EGL_DestroySurface(_this, data->egl_surface);
261 data->egl_surface = EGL_NO_SURFACE;
264 /* GL Context handling is done in the event loop because this function is run from the Java thread */
269 JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_onNativeKeyDown(
270 JNIEnv* env, jclass jcls, jint keycode)
272 Android_OnKeyDown(keycode);
276 JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_onNativeKeyUp(
277 JNIEnv* env, jclass jcls, jint keycode)
279 Android_OnKeyUp(keycode);
282 /* Keyboard Focus Lost */
283 JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_onNativeKeyboardFocusLost(
284 JNIEnv* env, jclass jcls)
286 /* Calling SDL_StopTextInput will take care of hiding the keyboard and cleaning up the DummyText widget */
292 JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_onNativeTouch(
293 JNIEnv* env, jclass jcls,
294 jint touch_device_id_in, jint pointer_finger_id_in,
295 jint action, jfloat x, jfloat y, jfloat p)
297 Android_OnTouch(touch_device_id_in, pointer_finger_id_in, action, x, y, p);
301 JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_onNativeMouse(
302 JNIEnv* env, jclass jcls,
303 jint button, jint action, jfloat x, jfloat y)
305 Android_OnMouse(button, action, x, y);
309 JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_onNativeAccel(
310 JNIEnv* env, jclass jcls,
311 jfloat x, jfloat y, jfloat z)
313 fLastAccelerometer[0] = x;
314 fLastAccelerometer[1] = y;
315 fLastAccelerometer[2] = z;
316 bHasNewData = SDL_TRUE;
320 JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_nativeLowMemory(
321 JNIEnv* env, jclass cls)
323 SDL_SendAppEvent(SDL_APP_LOWMEMORY);
327 JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_nativeQuit(
328 JNIEnv* env, jclass cls)
330 /* Discard previous events. The user should have handled state storage
331 * in SDL_APP_WILLENTERBACKGROUND. After nativeQuit() is called, no
332 * events other than SDL_QUIT and SDL_APP_TERMINATING should fire */
333 SDL_FlushEvents(SDL_FIRSTEVENT, SDL_LASTEVENT);
334 /* Inject a SDL_QUIT event */
336 SDL_SendAppEvent(SDL_APP_TERMINATING);
337 /* Resume the event loop so that the app can catch SDL_QUIT which
338 * should now be the top event in the event queue. */
339 if (!SDL_SemValue(Android_ResumeSem)) SDL_SemPost(Android_ResumeSem);
343 JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_nativePause(
344 JNIEnv* env, jclass cls)
346 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativePause()");
347 if (Android_Window) {
348 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_LOST, 0, 0);
349 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_MINIMIZED, 0, 0);
350 SDL_SendAppEvent(SDL_APP_WILLENTERBACKGROUND);
351 SDL_SendAppEvent(SDL_APP_DIDENTERBACKGROUND);
353 /* *After* sending the relevant events, signal the pause semaphore
354 * so the event loop knows to pause and (optionally) block itself */
355 if (!SDL_SemValue(Android_PauseSem)) SDL_SemPost(Android_PauseSem);
360 JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_nativeResume(
361 JNIEnv* env, jclass cls)
363 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeResume()");
365 if (Android_Window) {
366 SDL_SendAppEvent(SDL_APP_WILLENTERFOREGROUND);
367 SDL_SendAppEvent(SDL_APP_DIDENTERFOREGROUND);
368 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_GAINED, 0, 0);
369 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_RESTORED, 0, 0);
370 /* Signal the resume semaphore so the event loop knows to resume and restore the GL Context
371 * We can't restore the GL Context here because it needs to be done on the SDL main thread
372 * and this function will be called from the Java thread instead.
374 if (!SDL_SemValue(Android_ResumeSem)) SDL_SemPost(Android_ResumeSem);
378 JNIEXPORT void JNICALL Java_org_libsdl_app_SDLInputConnection_nativeCommitText(
379 JNIEnv* env, jclass cls,
380 jstring text, jint newCursorPosition)
382 const char *utftext = (*env)->GetStringUTFChars(env, text, NULL);
384 SDL_SendKeyboardText(utftext);
386 (*env)->ReleaseStringUTFChars(env, text, utftext);
389 JNIEXPORT void JNICALL Java_org_libsdl_app_SDLInputConnection_nativeSetComposingText(
390 JNIEnv* env, jclass cls,
391 jstring text, jint newCursorPosition)
393 const char *utftext = (*env)->GetStringUTFChars(env, text, NULL);
395 SDL_SendEditingText(utftext, 0, 0);
397 (*env)->ReleaseStringUTFChars(env, text, utftext);
400 JNIEXPORT jstring JNICALL Java_org_libsdl_app_SDLActivity_nativeGetHint(JNIEnv* env, jclass cls, jstring name) {
401 const char *utfname = (*env)->GetStringUTFChars(env, name, NULL);
402 const char *hint = SDL_GetHint(utfname);
404 jstring result = (*env)->NewStringUTF(env, hint);
405 (*env)->ReleaseStringUTFChars(env, name, utfname);
410 /*******************************************************************************
411 Functions called by SDL into Java
412 *******************************************************************************/
414 static int s_active = 0;
415 struct LocalReferenceHolder
421 static struct LocalReferenceHolder LocalReferenceHolder_Setup(const char *func)
423 struct LocalReferenceHolder refholder;
424 refholder.m_env = NULL;
425 refholder.m_func = func;
427 SDL_Log("Entering function %s", func);
432 static SDL_bool LocalReferenceHolder_Init(struct LocalReferenceHolder *refholder, JNIEnv *env)
434 const int capacity = 16;
435 if ((*env)->PushLocalFrame(env, capacity) < 0) {
436 SDL_SetError("Failed to allocate enough JVM local references");
440 refholder->m_env = env;
444 static void LocalReferenceHolder_Cleanup(struct LocalReferenceHolder *refholder)
447 SDL_Log("Leaving function %s", refholder->m_func);
449 if (refholder->m_env) {
450 JNIEnv* env = refholder->m_env;
451 (*env)->PopLocalFrame(env, NULL);
456 static SDL_bool LocalReferenceHolder_IsActive(void)
461 ANativeWindow* Android_JNI_GetNativeWindow(void)
465 JNIEnv *env = Android_JNI_GetEnv();
467 s = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetNativeSurface);
468 anw = ANativeWindow_fromSurface(env, s);
469 (*env)->DeleteLocalRef(env, s);
474 void Android_JNI_SetActivityTitle(const char *title)
477 JNIEnv *mEnv = Android_JNI_GetEnv();
478 mid = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,"setActivityTitle","(Ljava/lang/String;)Z");
480 jstring jtitle = (jstring)((*mEnv)->NewStringUTF(mEnv, title));
481 (*mEnv)->CallStaticBooleanMethod(mEnv, mActivityClass, mid, jtitle);
482 (*mEnv)->DeleteLocalRef(mEnv, jtitle);
486 SDL_bool Android_JNI_GetAccelerometerValues(float values[3])
489 SDL_bool retval = SDL_FALSE;
492 for (i = 0; i < 3; ++i) {
493 values[i] = fLastAccelerometer[i];
495 bHasNewData = SDL_FALSE;
502 static void Android_JNI_ThreadDestroyed(void* value)
504 /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */
505 JNIEnv *env = (JNIEnv*) value;
507 (*mJavaVM)->DetachCurrentThread(mJavaVM);
508 pthread_setspecific(mThreadKey, NULL);
512 JNIEnv* Android_JNI_GetEnv(void)
514 /* From http://developer.android.com/guide/practices/jni.html
515 * All threads are Linux threads, scheduled by the kernel.
516 * They're usually started from managed code (using Thread.start), but they can also be created elsewhere and then
517 * attached to the JavaVM. For example, a thread started with pthread_create can be attached with the
518 * JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a thread is attached, it has no JNIEnv,
519 * and cannot make JNI calls.
520 * Attaching a natively-created thread causes a java.lang.Thread object to be constructed and added to the "main"
521 * ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread on an already-attached thread
523 * Note: You can call this function any number of times for the same thread, there's no harm in it
527 int status = (*mJavaVM)->AttachCurrentThread(mJavaVM, &env, NULL);
529 LOGE("failed to attach current thread");
533 /* From http://developer.android.com/guide/practices/jni.html
534 * Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward,
535 * in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be
536 * called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific
537 * to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.)
538 * Note: The destructor is not called unless the stored value is != NULL
539 * Note: You can call this function any number of times for the same thread, there's no harm in it
540 * (except for some lost CPU cycles)
542 pthread_setspecific(mThreadKey, (void*) env);
547 int Android_JNI_SetupThread(void)
549 Android_JNI_GetEnv();
556 static jboolean audioBuffer16Bit = JNI_FALSE;
557 static jobject audioBuffer = NULL;
558 static void* audioBufferPinned = NULL;
560 int Android_JNI_OpenAudioDevice(int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
562 jboolean audioBufferStereo;
563 int audioBufferFrames;
565 JNIEnv *env = Android_JNI_GetEnv();
568 LOGE("callback_handler: failed to attach current thread");
570 Android_JNI_SetupThread();
572 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device");
573 audioBuffer16Bit = is16Bit;
574 audioBufferStereo = channelCount > 1;
576 if ((*env)->CallStaticIntMethod(env, mActivityClass, midAudioInit, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames) != 0) {
577 /* Error during audio initialization */
578 __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: error on AudioTrack initialization!");
582 /* Allocating the audio buffer from the Java side and passing it as the return value for audioInit no longer works on
583 * Android >= 4.2 due to a "stale global reference" error. So now we allocate this buffer directly from this side. */
586 jshortArray audioBufferLocal = (*env)->NewShortArray(env, desiredBufferFrames * (audioBufferStereo ? 2 : 1));
587 if (audioBufferLocal) {
588 audioBuffer = (*env)->NewGlobalRef(env, audioBufferLocal);
589 (*env)->DeleteLocalRef(env, audioBufferLocal);
593 jbyteArray audioBufferLocal = (*env)->NewByteArray(env, desiredBufferFrames * (audioBufferStereo ? 2 : 1));
594 if (audioBufferLocal) {
595 audioBuffer = (*env)->NewGlobalRef(env, audioBufferLocal);
596 (*env)->DeleteLocalRef(env, audioBufferLocal);
600 if (audioBuffer == NULL) {
601 __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: could not allocate an audio buffer!");
605 jboolean isCopy = JNI_FALSE;
606 if (audioBuffer16Bit) {
607 audioBufferPinned = (*env)->GetShortArrayElements(env, (jshortArray)audioBuffer, &isCopy);
608 audioBufferFrames = (*env)->GetArrayLength(env, (jshortArray)audioBuffer);
610 audioBufferPinned = (*env)->GetByteArrayElements(env, (jbyteArray)audioBuffer, &isCopy);
611 audioBufferFrames = (*env)->GetArrayLength(env, (jbyteArray)audioBuffer);
613 if (audioBufferStereo) {
614 audioBufferFrames /= 2;
617 return audioBufferFrames;
620 void * Android_JNI_GetAudioBuffer(void)
622 return audioBufferPinned;
625 void Android_JNI_WriteAudioBuffer(void)
627 JNIEnv *mAudioEnv = Android_JNI_GetEnv();
629 if (audioBuffer16Bit) {
630 (*mAudioEnv)->ReleaseShortArrayElements(mAudioEnv, (jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
631 (*mAudioEnv)->CallStaticVoidMethod(mAudioEnv, mActivityClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
633 (*mAudioEnv)->ReleaseByteArrayElements(mAudioEnv, (jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT);
634 (*mAudioEnv)->CallStaticVoidMethod(mAudioEnv, mActivityClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer);
637 /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
640 void Android_JNI_CloseAudioDevice(void)
642 JNIEnv *env = Android_JNI_GetEnv();
644 (*env)->CallStaticVoidMethod(env, mActivityClass, midAudioQuit);
647 (*env)->DeleteGlobalRef(env, audioBuffer);
649 audioBufferPinned = NULL;
653 /* Test for an exception and call SDL_SetError with its detail if one occurs */
654 /* If the parameter silent is truthy then SDL_SetError() will not be called. */
655 static SDL_bool Android_JNI_ExceptionOccurred(SDL_bool silent)
657 SDL_assert(LocalReferenceHolder_IsActive());
658 JNIEnv *mEnv = Android_JNI_GetEnv();
660 jthrowable exception = (*mEnv)->ExceptionOccurred(mEnv);
661 if (exception != NULL) {
664 /* Until this happens most JNI operations have undefined behaviour */
665 (*mEnv)->ExceptionClear(mEnv);
668 jclass exceptionClass = (*mEnv)->GetObjectClass(mEnv, exception);
669 jclass classClass = (*mEnv)->FindClass(mEnv, "java/lang/Class");
671 mid = (*mEnv)->GetMethodID(mEnv, classClass, "getName", "()Ljava/lang/String;");
672 jstring exceptionName = (jstring)(*mEnv)->CallObjectMethod(mEnv, exceptionClass, mid);
673 const char* exceptionNameUTF8 = (*mEnv)->GetStringUTFChars(mEnv, exceptionName, 0);
675 mid = (*mEnv)->GetMethodID(mEnv, exceptionClass, "getMessage", "()Ljava/lang/String;");
676 jstring exceptionMessage = (jstring)(*mEnv)->CallObjectMethod(mEnv, exception, mid);
678 if (exceptionMessage != NULL) {
679 const char* exceptionMessageUTF8 = (*mEnv)->GetStringUTFChars(mEnv, exceptionMessage, 0);
680 SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
681 (*mEnv)->ReleaseStringUTFChars(mEnv, exceptionMessage, exceptionMessageUTF8);
683 SDL_SetError("%s", exceptionNameUTF8);
686 (*mEnv)->ReleaseStringUTFChars(mEnv, exceptionName, exceptionNameUTF8);
695 static int Internal_Android_JNI_FileOpen(SDL_RWops* ctx)
697 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
703 jobject assetManager;
706 jobject readableByteChannel;
707 jstring fileNameJString;
712 JNIEnv *mEnv = Android_JNI_GetEnv();
713 if (!LocalReferenceHolder_Init(&refs, mEnv)) {
717 fileNameJString = (jstring)ctx->hidden.androidio.fileNameRef;
718 ctx->hidden.androidio.position = 0;
720 /* context = SDLActivity.getContext(); */
721 mid = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
722 "getContext","()Landroid/content/Context;");
723 context = (*mEnv)->CallStaticObjectMethod(mEnv, mActivityClass, mid);
726 /* assetManager = context.getAssets(); */
727 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, context),
728 "getAssets", "()Landroid/content/res/AssetManager;");
729 assetManager = (*mEnv)->CallObjectMethod(mEnv, context, mid);
731 /* First let's try opening the file to obtain an AssetFileDescriptor.
732 * This method reads the files directly from the APKs using standard *nix calls
734 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, assetManager), "openFd", "(Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;");
735 inputStream = (*mEnv)->CallObjectMethod(mEnv, assetManager, mid, fileNameJString);
736 if (Android_JNI_ExceptionOccurred(SDL_TRUE)) {
740 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getStartOffset", "()J");
741 ctx->hidden.androidio.offset = (*mEnv)->CallLongMethod(mEnv, inputStream, mid);
742 if (Android_JNI_ExceptionOccurred(SDL_TRUE)) {
746 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getDeclaredLength", "()J");
747 ctx->hidden.androidio.size = (*mEnv)->CallLongMethod(mEnv, inputStream, mid);
748 if (Android_JNI_ExceptionOccurred(SDL_TRUE)) {
752 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getFileDescriptor", "()Ljava/io/FileDescriptor;");
753 fd = (*mEnv)->CallObjectMethod(mEnv, inputStream, mid);
754 fdCls = (*mEnv)->GetObjectClass(mEnv, fd);
755 descriptor = (*mEnv)->GetFieldID(mEnv, fdCls, "descriptor", "I");
756 ctx->hidden.androidio.fd = (*mEnv)->GetIntField(mEnv, fd, descriptor);
757 ctx->hidden.androidio.assetFileDescriptorRef = (*mEnv)->NewGlobalRef(mEnv, inputStream);
759 /* Seek to the correct offset in the file. */
760 lseek(ctx->hidden.androidio.fd, (off_t)ctx->hidden.androidio.offset, SEEK_SET);
764 /* Disabled log message because of spam on the Nexus 7 */
765 /* __android_log_print(ANDROID_LOG_DEBUG, "SDL", "Falling back to legacy InputStream method for opening file"); */
767 /* Try the old method using InputStream */
768 ctx->hidden.androidio.assetFileDescriptorRef = NULL;
770 /* inputStream = assetManager.open(<filename>); */
771 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, assetManager),
772 "open", "(Ljava/lang/String;I)Ljava/io/InputStream;");
773 inputStream = (*mEnv)->CallObjectMethod(mEnv, assetManager, mid, fileNameJString, 1 /* ACCESS_RANDOM */);
774 if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
775 /* Try fallback to APK expansion files */
776 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, context),
777 "openAPKExpansionInputStream", "(Ljava/lang/String;)Ljava/io/InputStream;");
779 SDL_SetError("No openAPKExpansionInputStream() in Java class");
780 goto failure; /* Java class is missing the required method */
782 inputStream = (*mEnv)->CallObjectMethod(mEnv, context, mid, fileNameJString);
784 /* Exception is checked first because it always needs to be cleared.
785 * If no exception occurred then the last SDL error message is kept.
787 if (Android_JNI_ExceptionOccurred(SDL_FALSE) || !inputStream) {
792 ctx->hidden.androidio.inputStreamRef = (*mEnv)->NewGlobalRef(mEnv, inputStream);
794 /* Despite all the visible documentation on [Asset]InputStream claiming
795 * that the .available() method is not guaranteed to return the entire file
796 * size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
797 * android/apis/content/ReadAsset.java imply that Android's
798 * AssetInputStream.available() /will/ always return the total file size
801 /* size = inputStream.available(); */
802 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream),
804 ctx->hidden.androidio.size = (long)(*mEnv)->CallIntMethod(mEnv, inputStream, mid);
805 if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
809 /* readableByteChannel = Channels.newChannel(inputStream); */
810 channels = (*mEnv)->FindClass(mEnv, "java/nio/channels/Channels");
811 mid = (*mEnv)->GetStaticMethodID(mEnv, channels,
813 "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
814 readableByteChannel = (*mEnv)->CallStaticObjectMethod(
815 mEnv, channels, mid, inputStream);
816 if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
820 ctx->hidden.androidio.readableByteChannelRef =
821 (*mEnv)->NewGlobalRef(mEnv, readableByteChannel);
823 /* Store .read id for reading purposes */
824 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, readableByteChannel),
825 "read", "(Ljava/nio/ByteBuffer;)I");
826 ctx->hidden.androidio.readMethod = mid;
833 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.fileNameRef);
835 if(ctx->hidden.androidio.inputStreamRef != NULL) {
836 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.inputStreamRef);
839 if(ctx->hidden.androidio.readableByteChannelRef != NULL) {
840 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.readableByteChannelRef);
843 if(ctx->hidden.androidio.assetFileDescriptorRef != NULL) {
844 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.assetFileDescriptorRef);
849 LocalReferenceHolder_Cleanup(&refs);
853 int Android_JNI_FileOpen(SDL_RWops* ctx,
854 const char* fileName, const char* mode)
856 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
857 JNIEnv *mEnv = Android_JNI_GetEnv();
860 if (!LocalReferenceHolder_Init(&refs, mEnv)) {
861 LocalReferenceHolder_Cleanup(&refs);
866 LocalReferenceHolder_Cleanup(&refs);
870 jstring fileNameJString = (*mEnv)->NewStringUTF(mEnv, fileName);
871 ctx->hidden.androidio.fileNameRef = (*mEnv)->NewGlobalRef(mEnv, fileNameJString);
872 ctx->hidden.androidio.inputStreamRef = NULL;
873 ctx->hidden.androidio.readableByteChannelRef = NULL;
874 ctx->hidden.androidio.readMethod = NULL;
875 ctx->hidden.androidio.assetFileDescriptorRef = NULL;
877 retval = Internal_Android_JNI_FileOpen(ctx);
878 LocalReferenceHolder_Cleanup(&refs);
882 size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
883 size_t size, size_t maxnum)
885 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
887 if (ctx->hidden.androidio.assetFileDescriptorRef) {
888 size_t bytesMax = size * maxnum;
889 if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && ctx->hidden.androidio.position + bytesMax > ctx->hidden.androidio.size) {
890 bytesMax = ctx->hidden.androidio.size - ctx->hidden.androidio.position;
892 size_t result = read(ctx->hidden.androidio.fd, buffer, bytesMax );
894 ctx->hidden.androidio.position += result;
895 LocalReferenceHolder_Cleanup(&refs);
896 return result / size;
898 LocalReferenceHolder_Cleanup(&refs);
901 jlong bytesRemaining = (jlong) (size * maxnum);
902 jlong bytesMax = (jlong) (ctx->hidden.androidio.size - ctx->hidden.androidio.position);
905 /* Don't read more bytes than those that remain in the file, otherwise we get an exception */
906 if (bytesRemaining > bytesMax) bytesRemaining = bytesMax;
908 JNIEnv *mEnv = Android_JNI_GetEnv();
909 if (!LocalReferenceHolder_Init(&refs, mEnv)) {
910 LocalReferenceHolder_Cleanup(&refs);
914 jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannelRef;
915 jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
916 jobject byteBuffer = (*mEnv)->NewDirectByteBuffer(mEnv, buffer, bytesRemaining);
918 while (bytesRemaining > 0) {
919 /* result = readableByteChannel.read(...); */
920 int result = (*mEnv)->CallIntMethod(mEnv, readableByteChannel, readMethod, byteBuffer);
922 if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
923 LocalReferenceHolder_Cleanup(&refs);
931 bytesRemaining -= result;
933 ctx->hidden.androidio.position += result;
935 LocalReferenceHolder_Cleanup(&refs);
936 return bytesRead / size;
940 size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
941 size_t size, size_t num)
943 SDL_SetError("Cannot write to Android package filesystem");
947 static int Internal_Android_JNI_FileClose(SDL_RWops* ctx, SDL_bool release)
949 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
952 JNIEnv *mEnv = Android_JNI_GetEnv();
954 if (!LocalReferenceHolder_Init(&refs, mEnv)) {
955 LocalReferenceHolder_Cleanup(&refs);
956 return SDL_SetError("Failed to allocate enough JVM local references");
961 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.fileNameRef);
964 if (ctx->hidden.androidio.assetFileDescriptorRef) {
965 jobject inputStream = (jobject)ctx->hidden.androidio.assetFileDescriptorRef;
966 jmethodID mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream),
968 (*mEnv)->CallVoidMethod(mEnv, inputStream, mid);
969 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.assetFileDescriptorRef);
970 if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
975 jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
977 /* inputStream.close(); */
978 jmethodID mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream),
980 (*mEnv)->CallVoidMethod(mEnv, inputStream, mid);
981 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.inputStreamRef);
982 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.readableByteChannelRef);
983 if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
993 LocalReferenceHolder_Cleanup(&refs);
998 Sint64 Android_JNI_FileSize(SDL_RWops* ctx)
1000 return ctx->hidden.androidio.size;
1003 Sint64 Android_JNI_FileSeek(SDL_RWops* ctx, Sint64 offset, int whence)
1005 if (ctx->hidden.androidio.assetFileDescriptorRef) {
1008 if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
1009 offset += ctx->hidden.androidio.offset;
1012 offset += ctx->hidden.androidio.position;
1013 if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
1014 offset += ctx->hidden.androidio.offset;
1017 offset = ctx->hidden.androidio.offset + ctx->hidden.androidio.size + offset;
1020 return SDL_SetError("Unknown value for 'whence'");
1024 off_t ret = lseek(ctx->hidden.androidio.fd, (off_t)offset, SEEK_SET);
1025 if (ret == -1) return -1;
1026 ctx->hidden.androidio.position = ret - ctx->hidden.androidio.offset;
1032 newPosition = offset;
1035 newPosition = ctx->hidden.androidio.position + offset;
1038 newPosition = ctx->hidden.androidio.size + offset;
1041 return SDL_SetError("Unknown value for 'whence'");
1044 /* Validate the new position */
1045 if (newPosition < 0) {
1046 return SDL_Error(SDL_EFSEEK);
1048 if (newPosition > ctx->hidden.androidio.size) {
1049 newPosition = ctx->hidden.androidio.size;
1052 Sint64 movement = newPosition - ctx->hidden.androidio.position;
1054 unsigned char buffer[4096];
1056 /* The easy case where we're seeking forwards */
1057 while (movement > 0) {
1058 Sint64 amount = sizeof (buffer);
1059 if (amount > movement) {
1062 size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount);
1064 /* Failed to read/skip the required amount, so fail */
1071 } else if (movement < 0) {
1072 /* We can't seek backwards so we have to reopen the file and seek */
1073 /* forwards which obviously isn't very efficient */
1074 Internal_Android_JNI_FileClose(ctx, SDL_FALSE);
1075 Internal_Android_JNI_FileOpen(ctx);
1076 Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
1080 return ctx->hidden.androidio.position;
1084 int Android_JNI_FileClose(SDL_RWops* ctx)
1086 return Internal_Android_JNI_FileClose(ctx, SDL_TRUE);
1089 /* returns a new global reference which needs to be released later */
1090 static jobject Android_JNI_GetSystemServiceObject(const char* name)
1092 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
1093 JNIEnv* env = Android_JNI_GetEnv();
1094 jobject retval = NULL;
1096 if (!LocalReferenceHolder_Init(&refs, env)) {
1097 LocalReferenceHolder_Cleanup(&refs);
1101 jstring service = (*env)->NewStringUTF(env, name);
1105 mid = (*env)->GetStaticMethodID(env, mActivityClass, "getContext", "()Landroid/content/Context;");
1106 jobject context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
1108 mid = (*env)->GetMethodID(env, mActivityClass, "getSystemServiceFromUiThread", "(Ljava/lang/String;)Ljava/lang/Object;");
1109 jobject manager = (*env)->CallObjectMethod(env, context, mid, service);
1111 (*env)->DeleteLocalRef(env, service);
1113 retval = manager ? (*env)->NewGlobalRef(env, manager) : NULL;
1114 LocalReferenceHolder_Cleanup(&refs);
1118 #define SETUP_CLIPBOARD(error) \
1119 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); \
1120 JNIEnv* env = Android_JNI_GetEnv(); \
1121 if (!LocalReferenceHolder_Init(&refs, env)) { \
1122 LocalReferenceHolder_Cleanup(&refs); \
1125 jobject clipboard = Android_JNI_GetSystemServiceObject("clipboard"); \
1127 LocalReferenceHolder_Cleanup(&refs); \
1131 #define CLEANUP_CLIPBOARD() \
1132 LocalReferenceHolder_Cleanup(&refs);
1134 int Android_JNI_SetClipboardText(const char* text)
1138 jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, clipboard), "setText", "(Ljava/lang/CharSequence;)V");
1139 jstring string = (*env)->NewStringUTF(env, text);
1140 (*env)->CallVoidMethod(env, clipboard, mid, string);
1141 (*env)->DeleteGlobalRef(env, clipboard);
1142 (*env)->DeleteLocalRef(env, string);
1144 CLEANUP_CLIPBOARD();
1149 char* Android_JNI_GetClipboardText(void)
1151 SETUP_CLIPBOARD(SDL_strdup(""))
1153 jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, clipboard), "getText", "()Ljava/lang/CharSequence;");
1154 jobject sequence = (*env)->CallObjectMethod(env, clipboard, mid);
1155 (*env)->DeleteGlobalRef(env, clipboard);
1157 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, sequence), "toString", "()Ljava/lang/String;");
1158 jstring string = (jstring)((*env)->CallObjectMethod(env, sequence, mid));
1159 const char* utf = (*env)->GetStringUTFChars(env, string, 0);
1161 char* text = SDL_strdup(utf);
1162 (*env)->ReleaseStringUTFChars(env, string, utf);
1164 CLEANUP_CLIPBOARD();
1170 CLEANUP_CLIPBOARD();
1172 return SDL_strdup("");
1175 SDL_bool Android_JNI_HasClipboardText(void)
1177 SETUP_CLIPBOARD(SDL_FALSE)
1179 jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, clipboard), "hasText", "()Z");
1180 jboolean has = (*env)->CallBooleanMethod(env, clipboard, mid);
1181 (*env)->DeleteGlobalRef(env, clipboard);
1183 CLEANUP_CLIPBOARD();
1185 return has ? SDL_TRUE : SDL_FALSE;
1189 /* returns 0 on success or -1 on error (others undefined then)
1190 * returns truthy or falsy value in plugged, charged and battery
1191 * returns the value in seconds and percent or -1 if not available
1193 int Android_JNI_GetPowerInfo(int* plugged, int* charged, int* battery, int* seconds, int* percent)
1195 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
1196 JNIEnv* env = Android_JNI_GetEnv();
1197 if (!LocalReferenceHolder_Init(&refs, env)) {
1198 LocalReferenceHolder_Cleanup(&refs);
1204 mid = (*env)->GetStaticMethodID(env, mActivityClass, "getContext", "()Landroid/content/Context;");
1205 jobject context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
1207 jstring action = (*env)->NewStringUTF(env, "android.intent.action.BATTERY_CHANGED");
1209 jclass cls = (*env)->FindClass(env, "android/content/IntentFilter");
1211 mid = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/lang/String;)V");
1212 jobject filter = (*env)->NewObject(env, cls, mid, action);
1214 (*env)->DeleteLocalRef(env, action);
1216 mid = (*env)->GetMethodID(env, mActivityClass, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;");
1217 jobject intent = (*env)->CallObjectMethod(env, context, mid, NULL, filter);
1219 (*env)->DeleteLocalRef(env, filter);
1221 cls = (*env)->GetObjectClass(env, intent);
1224 jmethodID imid = (*env)->GetMethodID(env, cls, "getIntExtra", "(Ljava/lang/String;I)I");
1226 #define GET_INT_EXTRA(var, key) \
1227 iname = (*env)->NewStringUTF(env, key); \
1228 int var = (*env)->CallIntMethod(env, intent, imid, iname, -1); \
1229 (*env)->DeleteLocalRef(env, iname);
1232 jmethodID bmid = (*env)->GetMethodID(env, cls, "getBooleanExtra", "(Ljava/lang/String;Z)Z");
1234 #define GET_BOOL_EXTRA(var, key) \
1235 bname = (*env)->NewStringUTF(env, key); \
1236 int var = (*env)->CallBooleanMethod(env, intent, bmid, bname, JNI_FALSE); \
1237 (*env)->DeleteLocalRef(env, bname);
1240 GET_INT_EXTRA(plug, "plugged") /* == BatteryManager.EXTRA_PLUGGED (API 5) */
1242 LocalReferenceHolder_Cleanup(&refs);
1245 /* 1 == BatteryManager.BATTERY_PLUGGED_AC */
1246 /* 2 == BatteryManager.BATTERY_PLUGGED_USB */
1247 *plugged = (0 < plug) ? 1 : 0;
1251 GET_INT_EXTRA(status, "status") /* == BatteryManager.EXTRA_STATUS (API 5) */
1253 LocalReferenceHolder_Cleanup(&refs);
1256 /* 5 == BatteryManager.BATTERY_STATUS_FULL */
1257 *charged = (status == 5) ? 1 : 0;
1261 GET_BOOL_EXTRA(present, "present") /* == BatteryManager.EXTRA_PRESENT (API 5) */
1262 *battery = present ? 1 : 0;
1266 *seconds = -1; /* not possible */
1270 GET_INT_EXTRA(level, "level") /* == BatteryManager.EXTRA_LEVEL (API 5) */
1271 GET_INT_EXTRA(scale, "scale") /* == BatteryManager.EXTRA_SCALE (API 5) */
1272 if ((level == -1) || (scale == -1)) {
1273 LocalReferenceHolder_Cleanup(&refs);
1276 *percent = level * 100 / scale;
1279 (*env)->DeleteLocalRef(env, intent);
1281 LocalReferenceHolder_Cleanup(&refs);
1285 /* returns number of found touch devices as return value and ids in parameter ids */
1286 int Android_JNI_GetTouchDeviceIds(int **ids) {
1287 JNIEnv *env = Android_JNI_GetEnv();
1288 jint sources = 4098; /* == InputDevice.SOURCE_TOUCHSCREEN */
1289 jmethodID mid = (*env)->GetStaticMethodID(env, mActivityClass, "inputGetInputDeviceIds", "(I)[I");
1290 jintArray array = (jintArray) (*env)->CallStaticObjectMethod(env, mActivityClass, mid, sources);
1294 number = (int) (*env)->GetArrayLength(env, array);
1296 jint* elements = (*env)->GetIntArrayElements(env, array, NULL);
1299 *ids = SDL_malloc(number * sizeof (**ids));
1300 for (i = 0; i < number; ++i) { /* not assuming sizeof (jint) == sizeof (int) */
1301 (*ids)[i] = elements[i];
1303 (*env)->ReleaseIntArrayElements(env, array, elements, JNI_ABORT);
1306 (*env)->DeleteLocalRef(env, array);
1311 void Android_JNI_PollInputDevices(void)
1313 JNIEnv *env = Android_JNI_GetEnv();
1314 (*env)->CallStaticVoidMethod(env, mActivityClass, midPollInputDevices);
1317 /* See SDLActivity.java for constants. */
1318 #define COMMAND_SET_KEEP_SCREEN_ON 5
1320 /* sends message to be handled on the UI event dispatch thread */
1321 int Android_JNI_SendMessage(int command, int param)
1323 JNIEnv *env = Android_JNI_GetEnv();
1327 jmethodID mid = (*env)->GetStaticMethodID(env, mActivityClass, "sendMessage", "(II)Z");
1331 jboolean success = (*env)->CallStaticBooleanMethod(env, mActivityClass, mid, command, param);
1332 return success ? 0 : -1;
1335 void Android_JNI_SuspendScreenSaver(SDL_bool suspend)
1337 Android_JNI_SendMessage(COMMAND_SET_KEEP_SCREEN_ON, (suspend == SDL_FALSE) ? 0 : 1);
1340 void Android_JNI_ShowTextInput(SDL_Rect *inputRect)
1342 JNIEnv *env = Android_JNI_GetEnv();
1347 jmethodID mid = (*env)->GetStaticMethodID(env, mActivityClass, "showTextInput", "(IIII)Z");
1351 (*env)->CallStaticBooleanMethod(env, mActivityClass, mid,
1358 void Android_JNI_HideTextInput(void)
1360 /* has to match Activity constant */
1361 const int COMMAND_TEXTEDIT_HIDE = 3;
1362 Android_JNI_SendMessage(COMMAND_TEXTEDIT_HIDE, 0);
1365 int Android_JNI_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid)
1373 jintArray button_flags;
1374 jintArray button_ids;
1375 jobjectArray button_texts;
1381 env = Android_JNI_GetEnv();
1383 /* convert parameters */
1385 clazz = (*env)->FindClass(env, "java/lang/String");
1387 title = (*env)->NewStringUTF(env, messageboxdata->title);
1388 message = (*env)->NewStringUTF(env, messageboxdata->message);
1390 button_flags = (*env)->NewIntArray(env, messageboxdata->numbuttons);
1391 button_ids = (*env)->NewIntArray(env, messageboxdata->numbuttons);
1392 button_texts = (*env)->NewObjectArray(env, messageboxdata->numbuttons,
1394 for (i = 0; i < messageboxdata->numbuttons; ++i) {
1395 temp = messageboxdata->buttons[i].flags;
1396 (*env)->SetIntArrayRegion(env, button_flags, i, 1, &temp);
1397 temp = messageboxdata->buttons[i].buttonid;
1398 (*env)->SetIntArrayRegion(env, button_ids, i, 1, &temp);
1399 text = (*env)->NewStringUTF(env, messageboxdata->buttons[i].text);
1400 (*env)->SetObjectArrayElement(env, button_texts, i, text);
1401 (*env)->DeleteLocalRef(env, text);
1404 if (messageboxdata->colorScheme) {
1405 colors = (*env)->NewIntArray(env, SDL_MESSAGEBOX_COLOR_MAX);
1406 for (i = 0; i < SDL_MESSAGEBOX_COLOR_MAX; ++i) {
1407 temp = (0xFF << 24) |
1408 (messageboxdata->colorScheme->colors[i].r << 16) |
1409 (messageboxdata->colorScheme->colors[i].g << 8) |
1410 (messageboxdata->colorScheme->colors[i].b << 0);
1411 (*env)->SetIntArrayRegion(env, colors, i, 1, &temp);
1417 (*env)->DeleteLocalRef(env, clazz);
1421 mid = (*env)->GetStaticMethodID(env, mActivityClass, "getContext","()Landroid/content/Context;");
1423 context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
1425 clazz = (*env)->GetObjectClass(env, context);
1427 mid = (*env)->GetMethodID(env, clazz,
1428 "messageboxShowMessageBox", "(ILjava/lang/String;Ljava/lang/String;[I[I[Ljava/lang/String;[I)I");
1429 *buttonid = (*env)->CallIntMethod(env, context, mid,
1430 messageboxdata->flags,
1438 (*env)->DeleteLocalRef(env, context);
1439 (*env)->DeleteLocalRef(env, clazz);
1441 /* delete parameters */
1443 (*env)->DeleteLocalRef(env, title);
1444 (*env)->DeleteLocalRef(env, message);
1445 (*env)->DeleteLocalRef(env, button_flags);
1446 (*env)->DeleteLocalRef(env, button_ids);
1447 (*env)->DeleteLocalRef(env, button_texts);
1448 (*env)->DeleteLocalRef(env, colors);
1454 //////////////////////////////////////////////////////////////////////////////
1456 // Functions exposed to SDL applications in SDL_system.h
1457 //////////////////////////////////////////////////////////////////////////////
1460 void *SDL_AndroidGetJNIEnv()
1462 return Android_JNI_GetEnv();
1467 void *SDL_AndroidGetActivity()
1469 /* See SDL_system.h for caveats on using this function. */
1473 JNIEnv *env = Android_JNI_GetEnv();
1478 /* return SDLActivity.getContext(); */
1479 mid = (*env)->GetStaticMethodID(env, mActivityClass,
1480 "getContext","()Landroid/content/Context;");
1481 return (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
1484 const char * SDL_AndroidGetInternalStoragePath()
1486 static char *s_AndroidInternalFilesPath = NULL;
1488 if (!s_AndroidInternalFilesPath) {
1489 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
1496 JNIEnv *env = Android_JNI_GetEnv();
1497 if (!LocalReferenceHolder_Init(&refs, env)) {
1498 LocalReferenceHolder_Cleanup(&refs);
1502 /* context = SDLActivity.getContext(); */
1503 mid = (*env)->GetStaticMethodID(env, mActivityClass,
1504 "getContext","()Landroid/content/Context;");
1505 context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
1507 /* fileObj = context.getFilesDir(); */
1508 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
1509 "getFilesDir", "()Ljava/io/File;");
1510 fileObject = (*env)->CallObjectMethod(env, context, mid);
1512 SDL_SetError("Couldn't get internal directory");
1513 LocalReferenceHolder_Cleanup(&refs);
1517 /* path = fileObject.getAbsolutePath(); */
1518 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject),
1519 "getAbsolutePath", "()Ljava/lang/String;");
1520 pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid);
1522 path = (*env)->GetStringUTFChars(env, pathString, NULL);
1523 s_AndroidInternalFilesPath = SDL_strdup(path);
1524 (*env)->ReleaseStringUTFChars(env, pathString, path);
1526 LocalReferenceHolder_Cleanup(&refs);
1528 return s_AndroidInternalFilesPath;
1531 int SDL_AndroidGetExternalStorageState()
1533 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
1536 jstring stateString;
1540 JNIEnv *env = Android_JNI_GetEnv();
1541 if (!LocalReferenceHolder_Init(&refs, env)) {
1542 LocalReferenceHolder_Cleanup(&refs);
1546 cls = (*env)->FindClass(env, "android/os/Environment");
1547 mid = (*env)->GetStaticMethodID(env, cls,
1548 "getExternalStorageState", "()Ljava/lang/String;");
1549 stateString = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid);
1551 state = (*env)->GetStringUTFChars(env, stateString, NULL);
1553 /* Print an info message so people debugging know the storage state */
1554 __android_log_print(ANDROID_LOG_INFO, "SDL", "external storage state: %s", state);
1556 if (SDL_strcmp(state, "mounted") == 0) {
1557 stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ |
1558 SDL_ANDROID_EXTERNAL_STORAGE_WRITE;
1559 } else if (SDL_strcmp(state, "mounted_ro") == 0) {
1560 stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ;
1564 (*env)->ReleaseStringUTFChars(env, stateString, state);
1566 LocalReferenceHolder_Cleanup(&refs);
1570 const char * SDL_AndroidGetExternalStoragePath()
1572 static char *s_AndroidExternalFilesPath = NULL;
1574 if (!s_AndroidExternalFilesPath) {
1575 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
1582 JNIEnv *env = Android_JNI_GetEnv();
1583 if (!LocalReferenceHolder_Init(&refs, env)) {
1584 LocalReferenceHolder_Cleanup(&refs);
1588 /* context = SDLActivity.getContext(); */
1589 mid = (*env)->GetStaticMethodID(env, mActivityClass,
1590 "getContext","()Landroid/content/Context;");
1591 context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
1593 /* fileObj = context.getExternalFilesDir(); */
1594 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
1595 "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;");
1596 fileObject = (*env)->CallObjectMethod(env, context, mid, NULL);
1598 SDL_SetError("Couldn't get external directory");
1599 LocalReferenceHolder_Cleanup(&refs);
1603 /* path = fileObject.getAbsolutePath(); */
1604 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject),
1605 "getAbsolutePath", "()Ljava/lang/String;");
1606 pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid);
1608 path = (*env)->GetStringUTFChars(env, pathString, NULL);
1609 s_AndroidExternalFilesPath = SDL_strdup(path);
1610 (*env)->ReleaseStringUTFChars(env, pathString, path);
1612 LocalReferenceHolder_Cleanup(&refs);
1614 return s_AndroidExternalFilesPath;
1617 jclass Android_JNI_GetActivityClass(void)
1619 return mActivityClass;
1622 #endif /* __ANDROID__ */
1624 /* vi: set ts=4 sw=4 expandtab: */